How do I connect an SSL secured Web Service using SOAP

Posted by Gunnar.Vogt on 05-Jan-2015 01:19

Hi,

I'm having trouble to connect to 3rd-party web service (this service is stable and works with PHP and Java clients just fine). My ABLcode (OpenEdge 10.2) generates the error:

"HTTP Status 500 - Request processing failed; nested exception is org.springframework.ws.soap.saaj.SaajSoapMessageException: Could not access envelope: Unable to create envelope from given source: ; nested exception is com.sun.xml.messaging.saaj.SOAPExceptionImpl: Unable to create envelope from given source:"

This code derived from multiple sources and is my first attempt to access a Web Service via a secured SOAP connection. I hope someone in our community is able to see my mistake(s).

FYI: At the moment I'm using FIDDLER to generate a proxy that can track the secured communication between my client and the server. This seems to be the only solution so far to trap any valuable error messages from the server.

Thanks,
Gunnar

  define variable hWebService as handle    no-undo.
  define variable hSERVICE    as handle    no-undo.
  define variable ierror      as integer   no-undo.
  define variable cerror      as character no-undo.
  define variable ghHeader    as handle    no-undo.

  define variable iParam as character no-undo init '###########'.
  define variable oParam as logical   no-undo.

/*---------------------------------------------------------------------------*/ 
/* MAIN                                                                      */
/*---------------------------------------------------------------------------*/
 
do on error undo, throw:

  /*-------------------------------------------------------------------------*/
  /* Connect to Web Service                                                  */
  /*-------------------------------------------------------------------------*/

  /* Build global SOAP request header containing UsernameToken element */

  run BuildRequestHeader (output ghHeader).

  /* connect to Web Service and get WSDL */

  create server hWebService.
  hWebService:connect("-WSDL 'https://###############.wsdl' -nohostverify").

  run SERVICE set hSERVICE on hWebService.

  /* Associate the request callback with the service port */

  hSERVICE:set-callback-procedure ("REQUEST-HEADER",  "ReqHandler").
  /* hSERVICE:set-callback-procedure ("RESPONSE-HEADER", "ResHandler"). */

  /*-------------------------------------------------------------------------*/
  /* Call Web Service                                                        */
  /*-------------------------------------------------------------------------*/

  /* !!!!! now the trouble starts.  !!!!!! */
  run proc######### in hSERVICE (iParam, output oParam).

  message value1  view-as alert-box.


  /*-------------------------------------------------------------------------*/
  /* Error Handling                                                          */
  /*-------------------------------------------------------------------------*/
  
  catch mySoapErrorObject as progress.lang.soapfaulterror:
  
    do ierror = 1 to mysoaperrorobject:nummessages:

      cerror = cerror + substitute ('[soap &1] &2', ierror,  mysoaperrorobject:getmessage(ierror)) + "~n".

    end.
    delete object mysoaperrorobject.
  
  end catch.
  
  catch mySystemErrorObject as progress.lang.syserror:
  
    do ierror = 1 to mySystemErrorObject:nummessages:
  
      cerror = cerror + substitute ('system [&1] &2', ierror,  mySystemErrorObject:getmessage(ierror)) + "~n".
  
    end.
    delete object mySystemErrorObject.
  
  end catch.
  
  finally:
  
    delete procedure hSERVICE     no-error.
    hWebService:disconnect()  no-error.
    delete object hWebService no-error.
  
    if cerror <> "" then
      message "errors occured:" skip cerror view-as alert-box error.

  end.  /* finally */
end.

/*---------------------------------------------------------------------------*/ 
/* Service Definitions                                                       */
/*---------------------------------------------------------------------------*/

PROCEDURE proc#########:
  DEFINE INPUT PARAMETER iParam AS CHARACTER NO-UNDO.
  DEFINE OUTPUT PARAMETER oParam AS LOGICAL NO-UNDO.
END PROCEDURE.

/*---------------------------------------------------------------------------*/
/* Supporting Procedures                                                     */
/*---------------------------------------------------------------------------*/

procedure ReqHandler:
/*----------------------------------------------------------------------------*/
/*  Purpose:     Build Security SOAP header                                   */
/*  Parameters:  hHeader: handle for the x-document                           */
/*                                                                            */
/*  Notes:       OpenEdge® Development Web service dvwsv.pdf                  */
/*----------------------------------------------------------------------------*/

  define output parameter hHeader       as handle.
  define input  parameter cNamespace    as character.
  define input  parameter cLocalNS      as character.
  define output parameter lDeleteOnDone as logical.

  /* The IF test determines if this is the first request.
  If it is, then g_ghHeader is not set and hHeader is set
  to ? to ensure that no header is sent.
  ghHeader gets set when the response header is
  returned, so a subsequent pass through this code takes
  the previous response header and sends it as the
  current request header. */

  if valid-handle (ghHeader) then
    assign
      hHeader       = ghHeader
      lDeleteOnDone = no
      .
  else
    /* first response */
    assign      
      hHeader       = ?
      lDeleteOnDone = yes
      .

end procedure.

procedure ResHandler:
/*----------------------------------------------------------------------------*/
/*  Purpose:     Build Security SOAP header                                   */
/*  Parameters:  hHeader: handle for the x-document                           */
/*                                                                            */
/*  Notes:       OpenEdge® Development Web service dvwsv.pdf                  */
/*----------------------------------------------------------------------------*/

  define input parameter hHeader    as handle.
  define input parameter cNamespace as character.
  define input parameter cLocalNS   as character.

  message 'RESPONSE ResHandler' 
    skip cNamespace
    skip cLocalNS
    skip valid-handle (hHeader)
    skip valid-handle (ghHeader)
    view-as alert-box.

  /* If the g_header global variable
  is valid coming in, it has already been set
  in a previous response, therefore, delete
  the unnecessary response header object.
  Otherwise, set g-header to the response
  header object to pass back to the request
  header handler on subsequent requests. */
  if valid-handle (ghHeader) then
    delete object hHeader.
  else
    ghHeader = hHeader. /* first response */

end procedure.

procedure BuildRequestHeader:
/*----------------------------------------------------------------------------*/
/*  Purpose:     Build Security SOAP header                                   */
/*  Parameters:  hHeader: handle for the x-document                           */
/*                                                                            */
/*  Notes:       http://knowledgebase.progress.com/articles/Article/P140169?popup=true
*/
/*----------------------------------------------------------------------------*/

  define output parameter hHeader as handle.

  define variable cUsername       as character init '##########':U no-undo. /* user name not encrypted */
  define variable cPassword       as character init '##########':U no-undo. /* password encrypted */
  define variable cNonce          as character no-undo.                     /* UUID encrypted in multiple of 4 bytes 'N2RkYmMxODgxMTY0N2EzNWJiZjA':U */
  define variable cCreated        as character no-undo.                     /* '2012-03-06T10:09:47Z':U */
  define variable rawTemp         as raw       no-undo.
  define variable lcBuiltHeader   as longchar  no-undo.
  define variable hHeaderEntryRef as handle    no-undo.
  define variable cClientNS       as character init 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd':U no-undo.                           

  define variable hXDoc      as handle    no-undo.
  define variable hXNodeSec  as handle    no-undo.
  define variable hXNodeUNT  as handle    no-undo.
  define variable hXUsername as handle    no-undo.
  define variable hXPassword as handle    no-undo.
  define variable hXNonce    as handle    no-undo.
  define variable hXCreated  as handle    no-undo.
  
  /* timestamp of authentication
     Format   : Year (yyyy), “-”, month (mm), “-”, day (dd), “T”, hour (hh  24h), “:”, minutes (mm), “:”, seconds (ss), “Z”
     time zone: UTC
     e.g.     : 2012-03-06T10:55:17Z)
  */
  assign
    cCreated = string (datetime-tz (now,0)) /* '10/18/2014 23:04:50.541+00:00' */
    cCreated = substitute ('&1-&2-&3T&4Z':U,
                           substring (cCreated,  7, 4),
                           substring (cCreated,  1, 2),
                           substring (cCreated,  4, 2),
                           substring (cCreated, 12, 8))
    .

  /* “Nonce” value
     - random unique value - e.g. UUID)
     - must be multiple of 4 Bytes
     - in WSSE-Element the “Nonce” value is base64 crypted

     PHP code
     $uuid = uniqid( $prefix.'_', true);
     $uuid_md5 = md5( $uuid);
     $binary_nonce = substr( $uuid_md5, 0, 20);

     assign
       rawTemp = 'TEST' + generate-uuid
       rawTemp = md5-digest (rawTemp)  /* result is 16Byte raw */
       cNonce  = base64-encode (rawTemp)
       .
  */
  cNonce = substring (base64-encode (md5-digest (generate-uuid)), 1, 28).

  /* passwort is base64 crypted
     we need SHA1 Hash of binary „Nonce“ + timestamp + password

     PHP
     $ rawDigest = $binary_nonce.$timestamp.$password;
     $sha1 = sha1( $rawDigest, true);
     $digest = base64_encode( sha1);
  */
  cPassword = base64-encode (sha1-digest ( cNonce + cCreated + cPassword)).

  /* Build XML */

  create x-document hXDoc     .
  create x-noderef  hXNodeSec .
  create x-noderef  hXNodeUNT .
  create x-noderef  hXUsername.
  create x-noderef  hXPassword.
  create x-noderef  hXNonce   .
  create x-noderef  hXCreated .

  create soap-header          hHeader.
  create soap-header-entryref hHeaderEntryRef.
  hHeader:add-header-entry(hHeaderEntryRef).

  hXDoc:CREATE-NODE-NAMESPACE (hXNodeSec, cClientNS, 'wsse:Security':U, 'ELEMENT':U).
  hXNodeSec:SET-ATTRIBUTE ('xmlns:wsu':U,  'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
  hXDoc:CREATE-NODE-NAMESPACE (hXNodeUNT, cClientNS, 'wsse:UsernameToken':U, 'ELEMENT':U).
  hXNodeUNT:SET-ATTRIBUTE ('xmlns:wsu':U,  'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
  hXDoc:INSERT-BEFORE(hXNodeSec, ?).

    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Username':U, cUsername, input-output hXUsername).

    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Password':U, cPassword, input-output hXPassword).
    hXPassword:SET-ATTRIBUTE ('Type':U, 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest':U).

    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Nonce':U,    cNonce   , input-output hXNonce   ).
    hXNonce:SET-ATTRIBUTE ('EncodingType':U, 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary':U).
    
    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsu:Created':U,   cCreated , input-output hXCreated ).
    hXCreated:SET-ATTRIBUTE ("xmlns:wsu", 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
    
  hXNodeSec:APPEND-CHILD (hXNodeUNT).  

  /* Fill the header entry using a deep copy */

  hHeaderEntryRef:set-node (hXNodeSec).
  hHeaderEntryRef:set-must-understand (yes).

  /* save the XML for debugging */

  hXDoc:save ('file':U, 'c:\temp\header_ws-security_output.xml':U).

  finally:

    delete object hHeaderEntryRef no-error.
    delete object hXDoc           no-error.
    delete object hXNodeSec       no-error.
    delete object hXNodeUNT       no-error.
    delete object hXUsername      no-error.
    delete object hXPassword      no-error.
    delete object hXNonce         no-error.
    delete object hXCreated       no-error.

  end.  /* finally */

end procedure.

PROCEDURE yInsertXMLTextNode PRIVATE :
/*----------------------------------------------------------------------------*/
/*  Purpose:     Insert an XML Textnode in a specified parent node            */
/*  Parameters:  phDocument: handle for the x-document                        */
/*               phParent  : parent of the node created by this procedure     */
/*               pcLabel   : name of the new node                             */
/*               pcText    : text contained in the new node                   */
/*               phThisNode: handle of the newly created XML node             */
/*----------------------------------------------------------------------------*/
  define input        parameter phDocument as handle no-undo.
  define input        parameter phParent   as handle no-undo.
  define input        parameter pcLabel    as char   no-undo.
  define input        parameter pcText     as char   no-undo.
  define input-output parameter phThisNode as handle no-undo.

  define variable hChild2 as handle no-undo.

  Main:
  do on error undo Main, leave Main:

    create x-noderef hChild2.

    phDocument:create-node(phThisNode,pcLabel,'Element':U).
    phParent:append-child(phThisNode).
    phDocument:create-node(hChild2,?,'Text':U).
    phThisNode:append-child(hChild2).
    hChild2:node-value = (if pcText = ? then
                              '?':U
                          else
                            pcText).
    return.

  end. /*Main:*/

  return error.

  finally:
  
    delete object hChild2 no-error.
    
  end.  /* finally */

END PROCEDURE.

Posted by Gunnar.Vogt on 16-Jan-2015 13:52

I finally got it to work. My two errors have been a faulty SOAP structure (see my fix in the previous post)  and the creation of an encrypted password. The final fix is processing raw values for NONCE, CREATED and PASSWORD.

The code below is the corrected procedure. Please note when comparing with first version that I now pass username and password as parameter.

procedure BuildRequestHeader:
/*----------------------------------------------------------------------------*/
/*  Purpose:     Build Security SOAP header                                   */
/*  Parameters:                                                               */
/*                                                                            */
/*  Notes:       http://knowledgebase.progress.com/articles/Article/P140169?popup=true */
/*----------------------------------------------------------------------------*/
  define input parameter pcUsername as character.
  define input parameter pcPassword as character.

  define variable cClientNS       as character no-undo init 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd':U.
  define variable cUsername       as character no-undo. /* user name not encrypted */
  define variable cPassword       as character no-undo. /* password encrypted */
  define variable cNonce          as character no-undo. /* UUID encrypted in multiple of 4 bytes */
  define variable cCreated        as character no-undo. /* timestamp like '2012-03-06T10:09:47Z':U */
  define variable rawPassword     as raw       no-undo.
  define variable rawNonce        as raw       no-undo.
  define variable rawCreated      as raw       no-undo.
  define variable iPassword       as integer   no-undo. 
  define variable iNonce          as integer   no-undo init 16.                           
  define variable iCreated        as integer   no-undo.                           
  define variable hHeaderEntryRef as handle    no-undo.
  define variable hXDoc           as handle    no-undo.
  define variable hXNodeSec       as handle    no-undo.
  define variable hXNodeUNT       as handle    no-undo.
  define variable hXUsername      as handle    no-undo.
  define variable hXPassword      as handle    no-undo.
  define variable hXNonce         as handle    no-undo.
  define variable hXCreated       as handle    no-undo.
  define variable rawTemp         as raw       no-undo.

  /* init */

  assign
    cUsername = pcUsername 
    cPassword = pcPassword 
    .

  delete object ghHeader no-error.
  
  /* timestamp of authentication
     Format   : Year (yyyy), “-”, month (mm), “-”, day (dd), “T”, hour (hh  24h), “:”, minutes (mm), “:”, seconds (ss), “Z”
     time zone: UTC
     e.g.     : 2012-03-06T10:55:17Z)
  */
  assign
    cCreated = string (datetime-tz (now,0)) /* '10/18/2014 23:04:50.541+00:00' */
    cCreated = substitute ('&1-&2-&3T&4Z':U,
                           substring (cCreated,  7, 4),
                           substring (cCreated,  1, 2),
                           substring (cCreated,  4, 2),
                           substring (cCreated, 12, 8))
    .

  /* “Nonce” value
     - random unique value - e.g. UUID
     - must be multiple of 4 Bytes
     - in WSSE-Element the “Nonce” value is base64 crypted

     PHP code
     $uuid = uniqid( $prefix.'_', true);
     $uuid_md5 = md5( $uuid);
     $binary_nonce = substr( $uuid_md5, 0, 20);

     Note: sha1-digest      result is 20-byte binary
           generate-uuid    result is 16Byte raw 
           md5-digest       result is 16Byte binary
  */
  length (rawNonce)  = iNonce.

  assign
    rawTemp  = generate-uuid        
    rawTemp  = md5-digest (rawTemp) 
    rawNonce = get-bytes (rawTemp, 1, iNonce)
    cNonce   = base64-encode (rawNonce)
    .

  /* passwort is a simple base64-encoded SHA1 hash binary „Nonce“ + timestamp + password

     PHP
     $ rawDigest = $binary_nonce.$timestamp.$password;
     $sha1 = sha1( $rawDigest, true);
     $digest = base64_encode( sha1);

  */
  iCreated  = length (cCreated).
  iPassword = length (cPassword).

  /* reserve space */

  length (rawCreated)  = iCreated .
  length (rawPassword) = iPassword.

  /* convert char to raw */
 
  put-string (rawCreated,  1, length (cCreated))  = cCreated.
  put-string (rawPassword, 1, length (cPassword)) = cPassword.

  /* combine all 3 values into raw */

  length (rawTemp) = iNonce + iCreated + iPassword.

  put-bytes (rawTemp, 1                    ) = rawNonce.
  put-bytes (rawTemp, iNonce + 1           ) = rawCreated.
  put-bytes (rawTemp, iNonce + iCreated + 1) = rawPassword.

  cPassword = base64-encode (sha1-digest (rawTemp)).

  /* Build XML */

  create x-document hXDoc     .
  create x-noderef  hXNodeSec .
  create x-noderef  hXNodeUNT .
  create x-noderef  hXUsername.
  create x-noderef  hXPassword.
  create x-noderef  hXNonce   .
  create x-noderef  hXCreated .

  create soap-header          ghHeader.
  create soap-header-entryref hHeaderEntryRef.
  ghHeader:add-header-entry(hHeaderEntryRef).

  hXDoc:encoding = 'UTF-8':U.
  hXDoc:CREATE-NODE-NAMESPACE (hXNodeSec, cClientNS, 'wsse:Security':U, 'ELEMENT':U).
  hXNodeSec:SET-ATTRIBUTE ('xmlns:wsse':U, cClientNS). /* required otherwise SOAP request is missing this entry later */
  hXNodeSec:SET-ATTRIBUTE ('xmlns:wsu':U,  'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
  hXDoc:CREATE-NODE-NAMESPACE (hXNodeUNT, cClientNS, 'wsse:UsernameToken':U, 'ELEMENT':U).
  hXNodeUNT:SET-ATTRIBUTE ('xmlns:wsu':U,  'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
  hXDoc:INSERT-BEFORE(hXNodeSec, ?).

    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Username':U, cUsername, input-output hXUsername).

    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Password':U, cPassword, input-output hXPassword).
    hXPassword:SET-ATTRIBUTE ('Type':U, 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest':U).

    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Nonce':U,    cNonce   , input-output hXNonce   ).
    hXNonce:SET-ATTRIBUTE ('EncodingType':U, 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary':U).
    
    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsu:Created':U,   cCreated , input-output hXCreated ).
    hXCreated:SET-ATTRIBUTE ("xmlns:wsu", 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
    
  hXNodeSec:APPEND-CHILD (hXNodeUNT).  

  /* Fill the header entry using a deep copy */

  hHeaderEntryRef:set-node (hXNodeSec).
  hHeaderEntryRef:set-must-understand (yes).

  /* save XML for debugging
  hXDoc:save ('file':U, 'c:\temp\header_ws-security_output.xml':U).
  */

  finally:

    delete object hHeaderEntryRef no-error.
    delete object hXDoc           no-error.
    delete object hXNodeSec       no-error.
    delete object hXNodeUNT       no-error.
    delete object hXUsername      no-error.
    delete object hXPassword      no-error.
    delete object hXNonce         no-error.
    delete object hXCreated       no-error.

  end.  /* finally */

end procedure.


All Replies

Posted by WinningJr on 06-Jan-2015 08:25

The only difference I see between your code and my working example is:
 
run SERVICE set hSERVICE on hWebService.
 
run Soap set hSERVICE on hWebService.
 
There should still be a bunch of working examples on the PUG Challenge site calling the secured SFDC API from this past years session.
 
-Gerry
 
[collapse]
From: Gunnar.Vogt [mailto:bounce-GunnarVogt@community.progress.com]
Sent: Monday, January 05, 2015 2:20 AM
To: TU.OE.Development@community.progress.com
Subject: [Technical Users - OE Development] How do I connect an SSL secured Web Service
 
Thread created by Gunnar.Vogt

Hi,

I'm having trouble to connect to 3rd-party web service (this service is stable and works with PHP and Java clients just fine). My ABLcode (OpenEdge 10.2) generates the error:

"HTTP Status 500 - Request processing failed; nested exception is org.springframework.ws.soap.saaj.SaajSoapMessageException: Could not access envelope: Unable to create envelope from given source: ; nested exception is com.sun.xml.messaging.saaj.SOAPExceptionImpl: Unable to create envelope from given source:"

This code derived from multiple sources and is my first attempt to access a Web Service via a secured SOAP connection. I hope someone in our community is able to see my mistake(s).

FYI: At the moment I'm using FIDDLER to generate a proxy that can track the secured communication between my client and the server. This seems to be the only solution so far to trap any valuable error messages from the server.

Thanks,
Gunnar

  define variable hWebService as handle    no-undo.
  define variable hSERVICE    as handle    no-undo.
  define variable ierror      as integer   no-undo.
  define variable cerror      as character no-undo.
  define variable ghHeader    as handle    no-undo.
 
  define variable iParam as character no-undo init '###########'.
  define variable oParam as logical   no-undo.
 
/*---------------------------------------------------------------------------*/ 
/* MAIN                                                                      */
/*---------------------------------------------------------------------------*/
 
do on error undo, throw:
 
  /*-------------------------------------------------------------------------*/
  /* Connect to Web Service                                                  */
  /*-------------------------------------------------------------------------*/
 
  /* Build global SOAP request header containing UsernameToken element */
 
  run BuildRequestHeader (output ghHeader).
 
  /* connect to Web Service and get WSDL */
 
  create server hWebService.
  hWebService:connect("-WSDL 'https://###############.wsdl' -nohostverify").
 
  run SERVICE set hSERVICE on hWebService.
 
  /* Associate the request callback with the service port */
 
  hSERVICE:set-callback-procedure ("REQUEST-HEADER",  "ReqHandler").
  /* hSERVICE:set-callback-procedure ("RESPONSE-HEADER", "ResHandler"). */
 
  /*-------------------------------------------------------------------------*/
  /* Call Web Service                                                        */
  /*-------------------------------------------------------------------------*/
 
  /* !!!!! now the trouble starts.  !!!!!! */
  run proc######### in hSERVICE (iParam, output oParam).
 
  message value1  view-as alert-box.
 
 
  /*-------------------------------------------------------------------------*/
  /* Error Handling                                                          */
  /*-------------------------------------------------------------------------*/
  
  catch mySoapErrorObject as progress.lang.soapfaulterror:
  
    do ierror = 1 to mysoaperrorobject:nummessages:
 
      cerror = cerror + substitute ('[soap &1] &2', ierror,  mysoaperrorobject:getmessage(ierror)) + "~n".
 
    end.
    delete object mysoaperrorobject.
  
  end catch.
  
  catch mySystemErrorObject as progress.lang.syserror:
  
    do ierror = 1 to mySystemErrorObject:nummessages:
  
      cerror = cerror + substitute ('system [&1] &2', ierror,  mySystemErrorObject:getmessage(ierror)) + "~n".
  
    end.
    delete object mySystemErrorObject.
  
  end catch.
  
  finally:
  
    delete procedure hSERVICE     no-error.
    hWebService:disconnect()  no-error.
    delete object hWebService no-error.
  
    if cerror <> "" then
      message "errors occured:" skip cerror view-as alert-box error.
 
  end.  /* finally */
end.
 
/*---------------------------------------------------------------------------*/ 
/* Service Definitions                                                       */
/*---------------------------------------------------------------------------*/
 
PROCEDURE proc#########:
  DEFINE INPUT PARAMETER iParam AS CHARACTER NO-UNDO.
  DEFINE OUTPUT PARAMETER oParam AS LOGICAL NO-UNDO.
END PROCEDURE.
 
/*---------------------------------------------------------------------------*/
/* Supporting Procedures                                                     */
/*---------------------------------------------------------------------------*/
 
procedure ReqHandler:
/*----------------------------------------------------------------------------*/
/*  Purpose:     Build Security SOAP header                                   */
/*  Parameters:  hHeader: handle for the x-document                           */
/*                                                                            */
/*  Notes:       OpenEdge® Development Web service dvwsv.pdf                  */
/*----------------------------------------------------------------------------*/
 
  define output parameter hHeader       as handle.
  define input  parameter cNamespace    as character.
  define input  parameter cLocalNS      as character.
  define output parameter lDeleteOnDone as logical.
 
  /* The IF test determines if this is the first request.
  If it is, then g_ghHeader is not set and hHeader is set
  to ? to ensure that no header is sent.
  ghHeader gets set when the response header is
  returned, so a subsequent pass through this code takes
  the previous response header and sends it as the
  current request header. */
 
  if valid-handle (ghHeader) then
    assign
      hHeader       = ghHeader
      lDeleteOnDone = no
      .
  else
    /* first response */
    assign      
      hHeader       = ?
      lDeleteOnDone = yes
      .
 
end procedure.
 
procedure ResHandler:
/*----------------------------------------------------------------------------*/
/*  Purpose:     Build Security SOAP header                                   */
/*  Parameters:  hHeader: handle for the x-document                           */
/*                                                                            */
/*  Notes:       OpenEdge® Development Web service dvwsv.pdf                  */
/*----------------------------------------------------------------------------*/
 
  define input parameter hHeader    as handle.
  define input parameter cNamespace as character.
  define input parameter cLocalNS   as character.
 
  message 'RESPONSE ResHandler' 
    skip cNamespace
    skip cLocalNS
    skip valid-handle (hHeader)
    skip valid-handle (ghHeader)
    view-as alert-box.
 
  /* If the g_header global variable
  is valid coming in, it has already been set
  in a previous response, therefore, delete
  the unnecessary response header object.
  Otherwise, set g-header to the response
  header object to pass back to the request
  header handler on subsequent requests. */
  if valid-handle (ghHeader) then
    delete object hHeader.
  else
    ghHeader = hHeader. /* first response */
 
end procedure.
 
procedure BuildRequestHeader:
/*----------------------------------------------------------------------------*/
/*  Purpose:     Build Security SOAP header                                   */
/*  Parameters:  hHeader: handle for the x-document                           */
/*                                                                            */
/*  Notes:       http://knowledgebase.progress.com/articles/Article/P140169?popup=true
*/
/*----------------------------------------------------------------------------*/
 
  define output parameter hHeader as handle.
 
  define variable cUsername       as character init '##########':U no-undo. /* user name not encrypted */
  define variable cPassword       as character init '##########':U no-undo. /* password encrypted */
  define variable cNonce          as character no-undo.                     /* UUID encrypted in multiple of 4 bytes 'N2RkYmMxODgxMTY0N2EzNWJiZjA':U */
  define variable cCreated        as character no-undo.                     /* '2012-03-06T10:09:47Z':U */
  define variable rawTemp         as raw       no-undo.
  define variable lcBuiltHeader   as longchar  no-undo.
  define variable hHeaderEntryRef as handle    no-undo.
  define variable cClientNS       as character init 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd':U no-undo.                           
 
  define variable hXDoc      as handle    no-undo.
  define variable hXNodeSec  as handle    no-undo.
  define variable hXNodeUNT  as handle    no-undo.
  define variable hXUsername as handle    no-undo.
  define variable hXPassword as handle    no-undo.
  define variable hXNonce    as handle    no-undo.
  define variable hXCreated  as handle    no-undo.
  
  /* timestamp of authentication
     Format   : Year (yyyy), “-”, month (mm), “-”, day (dd), “T”, hour (hh  24h), “:”, minutes (mm), “:”, seconds (ss), “Z”
     time zone: UTC
     e.g.     : 2012-03-06T10:55:17Z)
  */
  assign
    cCreated = string (datetime-tz (now,0)) /* '10/18/2014 23:04:50.541+00:00' */
    cCreated = substitute ('&1-&2-&3T&4Z':U,
                           substring (cCreated,  7, 4),
                           substring (cCreated,  1, 2),
                           substring (cCreated,  4, 2),
                           substring (cCreated, 12, 8))
    .
 
  /* “Nonce” value
     - random unique value - e.g. UUID)
     - must be multiple of 4 Bytes
     - in WSSE-Element the “Nonce” value is base64 crypted
 
     PHP code
     $uuid = uniqid( $prefix.'_', true);
     $uuid_md5 = md5( $uuid);
     $binary_nonce = substr( $uuid_md5, 0, 20);
 
     assign
       rawTemp = 'TEST' + generate-uuid
       rawTemp = md5-digest (rawTemp)  /* result is 16Byte raw */
       cNonce  = base64-encode (rawTemp)
       .
  */
  cNonce = substring (base64-encode (md5-digest (generate-uuid)), 1, 28).
 
  /* passwort is base64 crypted
     we need SHA1 Hash of binary „Nonce“ + timestamp + password
 
     PHP
     $ rawDigest = $binary_nonce.$timestamp.$password;
     $sha1 = sha1( $rawDigest, true);
     $digest = base64_encode( sha1);
  */
  cPassword = base64-encode (sha1-digest ( cNonce + cCreated + cPassword)).
 
  /* Build XML */
 
  create x-document hXDoc     .
  create x-noderef  hXNodeSec .
  create x-noderef  hXNodeUNT .
  create x-noderef  hXUsername.
  create x-noderef  hXPassword.
  create x-noderef  hXNonce   .
  create x-noderef  hXCreated .
 
  create soap-header          hHeader.
  create soap-header-entryref hHeaderEntryRef.
  hHeader:add-header-entry(hHeaderEntryRef).
 
  hXDoc:CREATE-NODE-NAMESPACE (hXNodeSec, cClientNS, 'wsse:Security':U, 'ELEMENT':U).
  hXNodeSec:SET-ATTRIBUTE ('xmlns:wsu':U,  'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
  hXDoc:CREATE-NODE-NAMESPACE (hXNodeUNT, cClientNS, 'wsse:UsernameToken':U, 'ELEMENT':U).
  hXNodeUNT:SET-ATTRIBUTE ('xmlns:wsu':U,  'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
  hXDoc:INSERT-BEFORE(hXNodeSec, ?).
 
    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Username':U, cUsername, input-output hXUsername).
 
    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Password':U, cPassword, input-output hXPassword).
    hXPassword:SET-ATTRIBUTE ('Type':U, 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest':U).
 
    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Nonce':U,    cNonce   , input-output hXNonce   ).
    hXNonce:SET-ATTRIBUTE ('EncodingType':U, 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary':U).
    
    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsu:Created':U,   cCreated , input-output hXCreated ).
    hXCreated:SET-ATTRIBUTE ("xmlns:wsu", 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
    
  hXNodeSec:APPEND-CHILD (hXNodeUNT).  
 
  /* Fill the header entry using a deep copy */
 
  hHeaderEntryRef:set-node (hXNodeSec).
  hHeaderEntryRef:set-must-understand (yes).
 
  /* save the XML for debugging */
 
  hXDoc:save ('file':U, 'c:\temp\header_ws-security_output.xml':U).
 
  finally:
 
    delete object hHeaderEntryRef no-error.
    delete object hXDoc           no-error.
    delete object hXNodeSec       no-error.
    delete object hXNodeUNT       no-error.
    delete object hXUsername      no-error.
    delete object hXPassword      no-error.
    delete object hXNonce         no-error.
    delete object hXCreated       no-error.
 
  end.  /* finally */
 
end procedure.
 
PROCEDURE yInsertXMLTextNode PRIVATE :
/*----------------------------------------------------------------------------*/
/*  Purpose:     Insert an XML Textnode in a specified parent node            */
/*  Parameters:  phDocument: handle for the x-document                        */
/*               phParent  : parent of the node created by this procedure     */
/*               pcLabel   : name of the new node                             */
/*               pcText    : text contained in the new node                   */
/*               phThisNode: handle of the newly created XML node             */
[/collapse]

Posted by Gunnar.Vogt on 06-Jan-2015 14:33

Hi Gerry,

thanks for your quick response. I had yesterday a little breakthrough after I was able to use debug tool FIDDLER better. I compared a SOAP message sample that my Web Service provider gave me with my own created messages. The following additional line (the one with the remark) did the trick to form the message right.

  hXDoc:encoding = 'UTF-8':U.
  hXDoc:CREATE-NODE-NAMESPACE (hXNodeSec, cClientNS, 'wsse:Security':U, 'ELEMENT':U).
  hXNodeSec:SET-ATTRIBUTE ('xmlns:wsse':U, cClientNS). /* required otherwise SOAP request is missing this entry later */
  hXNodeSec:SET-ATTRIBUTE ('xmlns:wsu':U,  'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
  hXDoc:CREATE-NODE-NAMESPACE (hXNodeUNT, cClientNS, 'wsse:UsernameToken':U, 'ELEMENT':U).
  hXNodeUNT:SET-ATTRIBUTE ('xmlns:wsu':U,  'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
  hXDoc:INSERT-BEFORE(hXNodeSec, ?).


It seems redundant to put in the value of cClientNS twice, but that was the only way to set the Name Space right. Interestingly enough is that saving the SOAP header XML before its used in a SOAP message produces an XML that can be interpreted just fine.

I checked your code. "run Soap set hSERVICE on hWebService." will not work with target Web Service. The "SOAP" procedure you referring to must be an individual service provided by your Web Service only.

Thanks,

Gunnar

Posted by Gunnar.Vogt on 16-Jan-2015 13:52

I finally got it to work. My two errors have been a faulty SOAP structure (see my fix in the previous post)  and the creation of an encrypted password. The final fix is processing raw values for NONCE, CREATED and PASSWORD.

The code below is the corrected procedure. Please note when comparing with first version that I now pass username and password as parameter.

procedure BuildRequestHeader:
/*----------------------------------------------------------------------------*/
/*  Purpose:     Build Security SOAP header                                   */
/*  Parameters:                                                               */
/*                                                                            */
/*  Notes:       http://knowledgebase.progress.com/articles/Article/P140169?popup=true */
/*----------------------------------------------------------------------------*/
  define input parameter pcUsername as character.
  define input parameter pcPassword as character.

  define variable cClientNS       as character no-undo init 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd':U.
  define variable cUsername       as character no-undo. /* user name not encrypted */
  define variable cPassword       as character no-undo. /* password encrypted */
  define variable cNonce          as character no-undo. /* UUID encrypted in multiple of 4 bytes */
  define variable cCreated        as character no-undo. /* timestamp like '2012-03-06T10:09:47Z':U */
  define variable rawPassword     as raw       no-undo.
  define variable rawNonce        as raw       no-undo.
  define variable rawCreated      as raw       no-undo.
  define variable iPassword       as integer   no-undo. 
  define variable iNonce          as integer   no-undo init 16.                           
  define variable iCreated        as integer   no-undo.                           
  define variable hHeaderEntryRef as handle    no-undo.
  define variable hXDoc           as handle    no-undo.
  define variable hXNodeSec       as handle    no-undo.
  define variable hXNodeUNT       as handle    no-undo.
  define variable hXUsername      as handle    no-undo.
  define variable hXPassword      as handle    no-undo.
  define variable hXNonce         as handle    no-undo.
  define variable hXCreated       as handle    no-undo.
  define variable rawTemp         as raw       no-undo.

  /* init */

  assign
    cUsername = pcUsername 
    cPassword = pcPassword 
    .

  delete object ghHeader no-error.
  
  /* timestamp of authentication
     Format   : Year (yyyy), “-”, month (mm), “-”, day (dd), “T”, hour (hh  24h), “:”, minutes (mm), “:”, seconds (ss), “Z”
     time zone: UTC
     e.g.     : 2012-03-06T10:55:17Z)
  */
  assign
    cCreated = string (datetime-tz (now,0)) /* '10/18/2014 23:04:50.541+00:00' */
    cCreated = substitute ('&1-&2-&3T&4Z':U,
                           substring (cCreated,  7, 4),
                           substring (cCreated,  1, 2),
                           substring (cCreated,  4, 2),
                           substring (cCreated, 12, 8))
    .

  /* “Nonce” value
     - random unique value - e.g. UUID
     - must be multiple of 4 Bytes
     - in WSSE-Element the “Nonce” value is base64 crypted

     PHP code
     $uuid = uniqid( $prefix.'_', true);
     $uuid_md5 = md5( $uuid);
     $binary_nonce = substr( $uuid_md5, 0, 20);

     Note: sha1-digest      result is 20-byte binary
           generate-uuid    result is 16Byte raw 
           md5-digest       result is 16Byte binary
  */
  length (rawNonce)  = iNonce.

  assign
    rawTemp  = generate-uuid        
    rawTemp  = md5-digest (rawTemp) 
    rawNonce = get-bytes (rawTemp, 1, iNonce)
    cNonce   = base64-encode (rawNonce)
    .

  /* passwort is a simple base64-encoded SHA1 hash binary „Nonce“ + timestamp + password

     PHP
     $ rawDigest = $binary_nonce.$timestamp.$password;
     $sha1 = sha1( $rawDigest, true);
     $digest = base64_encode( sha1);

  */
  iCreated  = length (cCreated).
  iPassword = length (cPassword).

  /* reserve space */

  length (rawCreated)  = iCreated .
  length (rawPassword) = iPassword.

  /* convert char to raw */
 
  put-string (rawCreated,  1, length (cCreated))  = cCreated.
  put-string (rawPassword, 1, length (cPassword)) = cPassword.

  /* combine all 3 values into raw */

  length (rawTemp) = iNonce + iCreated + iPassword.

  put-bytes (rawTemp, 1                    ) = rawNonce.
  put-bytes (rawTemp, iNonce + 1           ) = rawCreated.
  put-bytes (rawTemp, iNonce + iCreated + 1) = rawPassword.

  cPassword = base64-encode (sha1-digest (rawTemp)).

  /* Build XML */

  create x-document hXDoc     .
  create x-noderef  hXNodeSec .
  create x-noderef  hXNodeUNT .
  create x-noderef  hXUsername.
  create x-noderef  hXPassword.
  create x-noderef  hXNonce   .
  create x-noderef  hXCreated .

  create soap-header          ghHeader.
  create soap-header-entryref hHeaderEntryRef.
  ghHeader:add-header-entry(hHeaderEntryRef).

  hXDoc:encoding = 'UTF-8':U.
  hXDoc:CREATE-NODE-NAMESPACE (hXNodeSec, cClientNS, 'wsse:Security':U, 'ELEMENT':U).
  hXNodeSec:SET-ATTRIBUTE ('xmlns:wsse':U, cClientNS). /* required otherwise SOAP request is missing this entry later */
  hXNodeSec:SET-ATTRIBUTE ('xmlns:wsu':U,  'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
  hXDoc:CREATE-NODE-NAMESPACE (hXNodeUNT, cClientNS, 'wsse:UsernameToken':U, 'ELEMENT':U).
  hXNodeUNT:SET-ATTRIBUTE ('xmlns:wsu':U,  'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
  hXDoc:INSERT-BEFORE(hXNodeSec, ?).

    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Username':U, cUsername, input-output hXUsername).

    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Password':U, cPassword, input-output hXPassword).
    hXPassword:SET-ATTRIBUTE ('Type':U, 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest':U).

    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsse:Nonce':U,    cNonce   , input-output hXNonce   ).
    hXNonce:SET-ATTRIBUTE ('EncodingType':U, 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary':U).
    
    run yInsertXMLTextNode (hXDoc, hXNodeUNT, 'wsu:Created':U,   cCreated , input-output hXCreated ).
    hXCreated:SET-ATTRIBUTE ("xmlns:wsu", 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd':U).
    
  hXNodeSec:APPEND-CHILD (hXNodeUNT).  

  /* Fill the header entry using a deep copy */

  hHeaderEntryRef:set-node (hXNodeSec).
  hHeaderEntryRef:set-must-understand (yes).

  /* save XML for debugging
  hXDoc:save ('file':U, 'c:\temp\header_ws-security_output.xml':U).
  */

  finally:

    delete object hHeaderEntryRef no-error.
    delete object hXDoc           no-error.
    delete object hXNodeSec       no-error.
    delete object hXNodeUNT       no-error.
    delete object hXUsername      no-error.
    delete object hXPassword      no-error.
    delete object hXNonce         no-error.
    delete object hXCreated       no-error.

  end.  /* finally */

end procedure.


Posted by Gunnar.Vogt on 18-Mar-2015 00:30

After working now for a while with SOAP I noticed two Progress specif problems (I'm working with OE 11.2B).:

1.) you cannot recreate the Web Service connection every time you like to access a function of your Web Service. This will lead to a run-time error after the 25th attempt. It's a bit odd... My workaround are global shared variables holding the handle for the Web Service and the Port. My application will fill the variable if need and will keep it for the entire run-time of the session. (By clearing this variable I can force a connection reset if needed)

2.) Progress has problems processing DateTime-TZ values if the Web Service has a different time format. In my case the Web Service reads and writes DateTime values with a format like this "2015-03-18 18:33:22 EEST". Progress will only process formats like "2015-03-18T18:33:22-05:00". My only workaround was to download the WSDL file and to change all "xs:dateTime" fields to "xs:string". My app has now to reference to this local copy and has to process character parameters instead of DateTime-TZ allowing me to overwrite the format. Progress seems to know this problem (see knowledgebase.progress.com/.../000048312).

Posted by bronco on 18-Mar-2015 02:56

well, on point 1) I think it preferable from a performance point of view to re-use instead of reconnect. Parsing the WSDL over and over again is quite time consuming (apart from the problem you mention, did you contact techsupport?).

The second point is clearly a problem with the other side since the rules (about datetime in XML, xsd:dateTime) are quite clear. IOW, the other side is misbehaving instead OpenEdge having a problem (at least that's my opinion).

Posted by Gunnar.Vogt on 18-Mar-2015 12:48

Hi "bronco"

1.) I agree. The performance is significantly different. I just don't like working with "global shared" variables e.g. and especially during testing phases. Since everything is working now the current outcome is preferred since it's faster ;-)

I did not contacted tech support.

2.) I started researching a bit more (books.xmlschemata.org/.../ch19-77049.html) and you are right. Progress does it by the books. So I will start a call with the Web Service provider.

Thanks for commenting. It helps me a lot to get  more insight in that matter.

Posted by bronco on 19-Mar-2015 03:24

Your welcome!

PS. my name is actually Bronco, so you can leave the quotes :-)

Posted by Liliana Santiesteban on 04-May-2018 07:36

Hi all,

I have  OpenEdge 10.2b and have error in the consumption of the SOAP webservice.

Error message: 9318

Error message: 9407

Error loading WSDL document  https://....../SoapApi/web/ServiceUpdatecustomer.svc?wsdl :

Fatal Error: connect operation failed (WinSock reported error=0)

  location: https://....../SoapApi/web/ServiceUpdatecustomer.svc?wsdl (11748)

Any help would be appreciated.

Many thanks.

This thread is closed