XQuery/Digest Authentication

From Wikibooks, open books for an open world
Jump to: navigation, search

Motivation[edit]

The 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[edit]

module 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[edit]

The 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[edit]

The 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[edit]

In 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]