XQuery/Digest Authentication
Motivation
editThe API you are using uses digest authentication, for example the Talis platform There is no direct support for this in the eXist httpclient module, but one can be written in XQuery.
The following implementation is based on the description and examples in Digest Authentication.
Modules and concepts
edit- eXist httpclient : for basic POST operation
- eXist util : for uuid generation and md5encoding
XQuery Module
editmodule namespace http ="http://www.cems.uwe.ac.uk/xmlwiki/http";
declare namespace httpclient= "http://exist-db.org/xquery/httpclient";
Two functions transform between a comma-delimited list of name="value" pairs and an XML representation:
The first function takes strings in the following format: string="value",string1="value2",string3="value3"
Note that the replace function removes all double quotes from the right side of each expression.
Supporting Functions
editThe following two functions convert key-value encoded strings of this form:
key1="value1",key2="value2",key3="value3"
into XML structures of the form:
<field name="key1" value="value1"/> <field name="key2" value="value2"/> <field name="key3" value="value3"/>
Here are the supporting functions:
declare function http:string-to-nvs($string) {
let $nameValues := tokenize($string,", ")
return
for $f in $nameValues
let $nv := tokenize($f,"=")
return <field name = "{$nv[1]}" value="{replace($nv[2],'"','')}"/>
};
declare function http:nvs-to-string($nvs) {
string-join(
for $field in $nvs
return
concat ($field/@name, '="',$field/@value,'" ')
, ", ")
};
Post With Digest Function
editThe main function handles a POST operation in two steps. The first POST will get a 401 response (should check this). The Digest is constructed and sent back with the second POST.
declare function http:post-with-digest($host, $path, $username, $password, $doc, $header ) {
let $uri := xs:anyURI(concat($host,$path))
(: send an HTTP Request to the server - called the challenge :)
let $request := httpclient:post( $uri, "dummy", false(), $header )
(: The server responds with the 401 response code. In this ressponse the server provide the authentication
realm and a randomly-generated, single-use value called a nonce.
We will get the realm and the nouce by finding the WWW-Authenticate value out of the response :)
let $first-response := substring-after($request//httpclient:header[@name="WWW-Authenticate"]/@value,"Digest ")
(: now we get the nounce, realm and the optional quality of protection out of the first response :)
let $fields := http:string-to-nvs($first-response)
let $nounce := $fields[@name="nonce"]/@value
let $realm := $fields[@name="realm"]/@value
let $qop := $fields[@name="qop"]/@value
(: Create a client nounce using a Universally Unique Identifier :)
let $cnonce := util:uuid()
(: this is the nounce count :)
let $nc := "00000001"
let $HA1:= util:md5(concat($username,":",$realm,":",$password))
(: TODO if the quality of protection (qos) is "auth-int" , then HA2 is
MD5(method : digestURU : MD5(entityBody))
But if qos "auth" or "auth-int" then it is the following :)
let $HA2 := util:md5(concat("POST:",$path))
let $response := util:md5(concat($HA1, ":", $nounce,":",$nc,":", $cnonce, ":", $qop, ":",$HA2))
(: note that if qop directive is unspecified, then the response should be md5(HA!:nounce:HA2) :)
(: here are the new headers :)
let $newfields := (
<field name="username" value="{$username}"/>,
<field name="uri" value="{$path}"/>,
<field name="cnonce" value="{$cnonce}"/>,
<field name="nc" value="{$nc}"/>,
<field name="response" value="{$response}"/>
)
let $authorization := concat("Digest ", http:nvs-to-string(($field,$newfields)))
let $header2 :=
<headers>
{$header/header}
<header name="Authorization"
value='{$authorization}'/>
</headers>
return httpclient:post( $uri, $doc, false(), $header2 )
};
Note that on under eXist 1.4 the util:md5($string) function has been deprecated. You should now use util:hash($string, 'md5) function with the second parameter now the type of hash.
Example
editIn this example, an RDF file is POSTed to the Talis server.
declare namespace rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; import module namespace http = "http://www.cems.uwe.ac.uk/xmlwiki/http" at "http.xqm"; let $rdf := doc("/db/RDF/dataset.rdf")/rdf:RDF let $path := "/store/mystore/meta" let $username := "myusername" let $password := "mypassword" let $host := "http://api.talis.com" let $header := <headers> <header name="Content-Type" value="application/rdf+xml"/> </headers> return http:put-with-digest($host, $path, $username, $password, $rdf , $header)
References
edit- http://en.wikipedia.org/wiki/Digest_access_authentication Wikipedia Page on Digest Authentication
- https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc780170(v=ws.10) Microsoft Technet Article