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.
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.
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]
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
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.
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).
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).
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.
Your welcome!
PS. my name is actually Bronco, so you can leave the quotes :-)
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.