Content of client module

xquery version "3.0";

(:
 : Copyright 2008-2011 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
:)

(:~
 : This module provides the functions necessary to acquire access to the personal
 : resources of a user through the open standard called
 : <a href="http://oauth.net/" target="_blank">OAuth</a>.
 : The application/mashup creator does not need to know the
 : specifics of <a href="http://oauth.net/" target="_blank">OAuth</a> to use this module.
 : @author Stephanie Russell
 : @author <a href="mailto:william.candillon@28msec.com">William Candillon</a>
 : @see <a href="http://oauth.net/" target="_blank">OAuth Website</a>
 : @project Zorba/OAuth/Client
 :
 :)
module namespace oauth = "http://www.zorba-xquery.com/modules/oauth/client";

import module namespace ra = "http://zorba.io/modules/random";
import module namespace hmac = "http://zorba.io/modules/hmac";
import module namespace http-client = "http://expath.org/ns/http-client";
import schema namespace http = "http://expath.org/ns/http-client";

import module namespace base64 = "http://zorba.io/modules/base64";

(:~
 : Use err module functions for throwing errors.
 :)
import module namespace oerr = "http://www.zorba-xquery.com/modules/oauth/error";

import schema namespace sp = "http://www.zorba-xquery.com/schemas/oauth/service-provider";
import schema namespace p = "http://www.zorba-xquery.com/schemas/oauth/parameters";

declare namespace an = "http://zorba.io/annotations";
declare namespace ver = "http://zorba.io/options/versioning";
declare namespace op = "http://zorba.io/options/features";
declare namespace f = "http://zorba.io/features";

declare option op:disable "f:trace";

declare option ver:module-version "2.0";

(:~
 : Utility function to build a service provider object.
 : This object contains the information required by the 
 : OAuth client to interact with an OAuth service provider.
 : For instance the following expression:
 : <pre class="ace-static" ace-mode="xquery">
 : let $consumer-key     := "#"
 : let $consumer-secret  := "#"
 : let $signature-method := "HMAC-SHA1"
 : let $realm            := "twitter.com"
 : let $authorize-url    := "http://api.twitter.com/oauth/authorize"
 : let $request-token-method := "POST"
 : let $request-token-url := "https://twitter.com/oauth/request_token"
 : let $request-token-callback-url := "https://twitter.com/oauth/request_token"
 : let $access-token-method := "POST"
 : let $access-token-url := "https://api.twitter.com/oauth/access_token"
 : return oauth:service-provider(
 :   $consumer-key, $consumer-secret, $signature-method,
 :   $realm, $authorize-url, $request-token-method,
 :   $request-token-url, $request-token-callback-url,
 :   $access-token-method, $access-token-url
 : )
 : </pre>
 : Will return the following XML schema instance:
 : <pre class="ace-static" ace-mode="xml">
 : <sp:service-provider xmlns:sp="http://www.zorba-xquery.com/schemas/oauth/service-provider"
 :     consumer-key="#" consumer-secret="#" signature-method="HMAC-SHA1"
 :     realm="twitter.com" authorize-url="http://api.twitter.com/oauth/authorize">
 :   <sp:request-token
 :       method="POST"
 :       href="https://twitter.com/oauth/request_token"
 :       callback-url="https://twitter.com/oauth/request_token"/>
 :   <sp:access-token
 :       method="POST"
 :       href="https://api.twitter.com/oauth/access_token"/>
 : </sp:service-provider>
 : </pre>
 : @param $consumer-key Client Identifier, also known as the consumer-key
 : @param $consumer-secret Client Shared-Secret, also known as the consumer-secret
 : @param $signature-method Method with which the signing key is signed (typically HMAC-SHA1)
 : @param $realm Realm that defines the protection space
 : @param $authorize-url authorize-url
 : @param $request-token-method request-token-method
 : @param $request-token-url request-token-url
 : @param $request-token-callback-url request-token-callback-url
 : @param $access-token-method access-token-method
 : @param $access-token-url access-token-url
 : @return instance of the OAuth service provider XML schema.
:)
declare function oauth:service-provider(
  $consumer-key as xs:string, $consumer-secret as xs:string, $signature-method as xs:string,
  $realm as xs:string, $authorize-url as xs:string, $request-token-method as xs:string,
  $request-token-url as xs:string, $request-token-callback-url as xs:string,
  $access-token-method as xs:string, $access-token-url as xs:string
)
as schema-element(sp:service-provider)
{
validate {
  element sp:service-provider {
    attribute consumer-key    { $consumer-key },
    attribute consumer-secret { $consumer-secret },
    attribute signature-method { $signature-method },
    attribute realm { $realm },
    attribute authorize-url { $authorize-url },
    element sp:request-token {
      attribute method { $request-token-method },
      attribute href { $request-token-url },
      attribute callback-url { $request-token-callback-url }
    },
    element sp:access-token {
      attribute method { $access-token-method },
      attribute href { $access-token-url }
    }
  }
}
};

(:~
 : Create an OAuth Parameters instance.
 : Instances of OAuth parameters are used to 
 : contain value/pair data such as <em>oauth_token</em>
 : and <em>oauth_token_secret</em>.
 : For instance the following code snippet:
 : <pre class="ace-static" ace-mode="xquery">
 :  oauth:parameters("oauth_token", "#")
 : </pre>
 : Returns the following XML schema instance:
 : <pre class="ace-static" ace-mode="xml">
 : <p:parameters xmlns:p="http://www.zorba-xquery.com/schemas/oauth/parameters">
 :   <p:parameter name="oauth_token" value="#" />
 : </p:parameters>
 : </pre>
 : @param $name parameter name
 : @param $value parameter value
 : @return instance of the OAuth parameters XML schema.
 :)
declare function oauth:parameters($name as xs:string, $value as xs:string)
as element(p:parameters)
{
  validate {
    <p:parameters>
      <p:parameter name="{$name}" value="{$value}" />
    </p:parameters>
  }
};

(:~
 : Adds an OAuth parameter to an OAuth Parameters instance.
 : Instances of OAuth parameters are used to 
 : contain value/pair data such as <em>oauth_token</em>
 : and <em>oauth_token_secret</em>.
 : For instance the following code snippet:
 : <pre class="ace-static" ace-mode="xquery">
 :  let $params := oauth:parameters("oauth_token", "#")
 :  let $params := oauth:add-parameter($params, "oauth_token_secret", "#")
 :  return $params
 : </pre>
 : Returns the following XML schema instance:
 : <pre class="ace-static" ace-mode="xquery">
 : <p:parameters xmlns:p="http://www.zorba-xquery.com/schemas/oauth/parameters">
 :   <p:parameter name="oauth_token" value="#" />
 :   <p:parameter name="oauth_token_secret" value="#" />
 : </p:parameters>
 : </pre>
 : @param $parameters parameters
 : @param $name parameter name
 : @param $value parameter value
 : @return instance of the OAuth parameters XML schema.
 :)
declare function oauth:add-parameter($parameters as schema-element(p:parameters)?, $name as xs:string, $value as xs:string)
as schema-element(p:parameters)
{
  validate {
    <p:parameters>
      {
        if($name != "") then
          <p:parameter name="{$name}" value="{$value}" />
        else (),
        $parameters/p:parameter
      }
    </p:parameters>
  }
};

(:~
 : This function returns the string value of the parameters whose key matches a $string input.
 : @param $params element parameters
 : @param $string string as the "key" name
 : @return string value of the parameter with key $string
 :
 : <pre class="ace-static" ace-mode="xquery">
 : let $params := oauth:parameters("oauth_token", "token")
 : let $params := oauth:add-parameter($params, "oauth_token_secret", "secret")
 : let $token-secret := oauth:parameter($params, "oauth_token_secret")
 : return $token-secret
 : </pre>
 :)
declare function oauth:parameter($params as schema-element(p:parameters), $string as xs:string) as xs:string
{
  let $value := 
    for $param in $params/p:parameter
    let $key := $param/@name/string(.)
    let $value := $param/@value/string(.)
    where $key = $string
    return $value
  return
    if(empty($value)) then
      error($oerr:OC005, concat("Parameter not found: ", $string))
    else
      xs:string($value)
      
};

(:~
 : This function allows the client to obtain a set of temporary credentials from the service provider by making an authenticated HTTP request to the Temporary Credential Request endpoint.
 : This function is provided for convenience for <a href="#request-token-2">request-token#2</a>.
 : Invoking this function is equivalent to:
 : <pre class="ace-static" ace-mode="xquery">
 : oauth:request-token($service-provider, ())
 : </pre>
 :
 : @see http://tools.ietf.org/html/rfc5849#section-2.1
 : @param $service-provider Information about the service provider
 : @return temporary credentials correctly parsed as parameter elements, or an error if http response status is not 200 OK
 :)
declare %an:sequential function oauth:request-token($service-provider as schema-element(sp:service-provider))
as schema-element(p:parameters)
{
  oauth:request-token($service-provider, ())
};

(:~
 : This function allows the client to obtain a set of temporary credentials from the service provider by making an authenticated HTTP request to the Temporary Credential Request endpoint.
 : This function is provided for convenience.
 : @see http://tools.ietf.org/html/rfc5849#section-2.1
 : @param $service-provider Information about the service provider
 : @param $parameters Additionnal parameters to the request
 : @return temporary credentials correctly parsed as parameter elements, or an error if http response status is not 200 OK
 :
 : <pre class="ace-static" ace-mode="xquery">
 : let $twitter-config := oauth:service-provider(...)
 : let $additional-parameter := oauth:parameters("foo", "bar")
 : let $tokens := oauth:request-token($twitter-config, $additional-parameter)
 : let $token := oauth:parameter($tokens, "oauth_token")
 : let $token-secret := oauth:parameter($tokens, "oauth_token_secret")
 : ...
 : </pre>
 :)
declare %an:sequential function oauth:request-token($service-provider as schema-element(sp:service-provider), $parameters as schema-element(p:parameters)?)
as schema-element(p:parameters)
{
  let $consumer-key     as xs:string := $service-provider/@consumer-key/string(.)
  let $consumer-secret  as xs:string := $service-provider/@consumer-secret/string(.)
  let $signature-method as xs:string := $service-provider/@signature-method/string(.)
  let $realm            as xs:string := $service-provider/@realm/string(.)
  let $callback-url     as xs:string := $service-provider/sp:request-token/@callback-url/string(.)
  let $method           as xs:string := $service-provider/sp:request-token/@method/string(.)
  let $temporary-credential-request as xs:string := $service-provider/sp:request-token/@href/string(.)
  let $additional-parameters := oauth:additional-parameters($parameters)
  return oauth:request-token(
    $consumer-key,
    $consumer-secret,
    $signature-method,
    $method,
    $realm,
    $temporary-credential-request,
    $callback-url,
    $additional-parameters
    )
};

(:~
 : This function allows the client to obtain a set of token credentials from the service provider by making an authenticated HTTP request to the Token Request endpoint.
 : This function is provided for convenience.
 : @see http://tools.ietf.org/html/rfc5849#section-2.3
 : @param $service-provider Contains service provider information
 : @param $parameters parameters
 : @return token credentials correctly parsed as parameter elements, or an error if http response status is not 200 OK
 :
 : <pre class="ace-static" ace-mode="xquery">
 : let $service-provider := oauth:service-provider(...)
 : let $parameters := oauth:parameters("oauth_token", "#")
 : let $parameters := oauth:add-parameter($parameters, "oauth_token_secret", "#")
 : let $tokens := oauth:access-token($service-provider, $parameters)
 : ...
 : </pre>
 :)
declare %an:sequential function oauth:access-token($service-provider as schema-element(sp:service-provider), $parameters as schema-element(p:parameters))
as schema-element(p:parameters)
{
  let $consumer-key       as xs:string := $service-provider/@consumer-key/string(.)
  let $consumer-secret    as xs:string := $service-provider/@consumer-secret/string(.)
  let $signature-method   as xs:string := $service-provider/@signature-method/string(.)
  let $realm              as xs:string := $service-provider/@realm/string(.) 
  let $method             as xs:string := $service-provider/sp:access-token/@method/string(.)
  let $token-request-uri  as xs:string := $service-provider/sp:access-token/@href/string(.)
  let $oauth-token        as xs:string := oauth:parameter($parameters,"oauth_token")
  let $oauth-token-secret as xs:string := oauth:parameter($parameters,"oauth_token_secret")
  let $additional-parameters := oauth:additional-parameters($parameters)
  return 
    oauth:access-token(
    $consumer-key,
    $consumer-secret,
    $signature-method,
    $realm,
    $oauth-token,
    $oauth-token-secret,
    $method,
    $token-request-uri,
    $additional-parameters
    )
};


(:~
 : This function allows the client access to the protected resources of the user.
 : This function is provided for convenience.
 : @see http://tools.ietf.org/html/rfc5849#section-3
 : @param $protected-resource (Not schema-validated) http:request element with http method and href.
 : @param $service-provider Information about the service provider
 : @param $parameters parameters
 : @return protected resources parsed as parameter elements, or an error if http response status is not 200 OK
 :
 : <pre class="ace-static" ace-mode="xquery">
 : let $tokens := oauth:parameters("oauth_token", "#")
 : let $tokens := oauth:add-parameter($tokens, "oauth_token_secret", "#")
 : let $service-provider := oauth:service-provider(...)
 : let $request := validate {
 :   <request xmlns="http://expath.org/ns/http-client"
 :            href="http://twitter.com/account/verify_credentials.xml"
 :            method="GET" />
 : }
 : return oauth:protected-resource($request, $service-provider, $tokens)                           
 : </pre>
 :)
declare %an:sequential function oauth:protected-resource(
  $protected-resource as schema-element(http:request),
  $service-provider as schema-element(sp:service-provider),
  $parameters as schema-element(p:parameters)
)
as item()*
{
  let $consumer-key        as xs:string := $service-provider/@consumer-key/string(.)
  let $consumer-secret     as xs:string := $service-provider/@consumer-secret/string(.)
  let $signature-method    as xs:string := $service-provider/@signature-method/string(.)
  let $realm               as xs:string := $service-provider/@realm/string(.) 
  let $oauth-token         as xs:string := oauth:parameter($parameters,"oauth_token")
  let $oauth-token-secret  as xs:string := oauth:parameter($parameters,"oauth_token_secret")
  let $additional-parameters := oauth:additional-parameters($parameters)
  return 
  oauth:protected-resource(
    $consumer-key,
    $consumer-secret,
    $signature-method,
    $oauth-token,
    $oauth-token-secret,
    $realm,
    $protected-resource,
    $additional-parameters)
};

(:~
 : The timestamp is expressed in the number of seconds since January 1, 1970 00:00:00 GMT.
 :
 : @see http://tools.ietf.org/html/rfc5849#section-3.3
 : @return integer time in seconds since Unix epoch
 :)
declare %private %an:nondeterministic function oauth:timestamp() as xs:decimal
{
  let $current-dateTime := fn:adjust-dateTime-to-timezone(current-dateTime(), xs:dayTimeDuration('PT0H'))
  let $duration := $current-dateTime - xs:dateTime("1970-01-01T00:00:00Z")
  return fn:round($duration div xs:dayTimeDuration('PT1S'))
};

(:~
 : This function returns a string key which is the client and temporary credential concatenated with an ampersand.
 :
 : @see http://tools.ietf.org/html/rfc5849#section-3.4.2
 : @param $oauth-consumer-secret Client credential referred to as the "consumer secret"
 : @param $oauth-token-secret Temporary credential referred to as the "oauth token secret"
 : @return String signing key
 :)
declare %private function oauth:key($oauth-consumer-secret as xs:string, $oauth-token-secret as xs:string?) as xs:string
{
  concat($oauth-consumer-secret, "&", 
    if(empty($oauth-token-secret)) 
    then "" 
    else $oauth-token-secret)
};

(:~
 : This function should return a new UUID every time the function is invoked.
 :
 : @see http://tools.ietf.org/html/rfc5849#section-3.3
 : @return random string
 :)
declare %private %an:nondeterministic function oauth:nonce() as xs:string
{
  ra:uuid()
};

(:~
 : This function normalizes parameters into a single string.
 :
 : @see http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
 : @param $params Element containing OAuth specific parameters
 : @param $divide String, usually an equal sign(=)
 : @param $option Optional string, quotation mark '"' in the creation of the authorization header
 : @param $comma String, typically a comma (,) in the authorization header
 : @return string formatted specifically for the authorization header, or for parameterization
 :)
declare %private function oauth:normalization($params as schema-element(p:parameters), $divide as xs:string, $option as xs:string?, $comma as xs:string) as xs:string
{
  fn:string-join(
    for $param in $params/p:parameter
    let $k := $param/@name/string(.)
    let $v := $param/@value/string(.)
    order by $param/@name
    where $param/@name != ""
    return concat($k, $divide, $v, $option),
    $comma
  )
};

(:~
 : This function formats the authorization header.
 :
 : @see http://tools.ietf.org/html/rfc5849#section-3.5.1
 : @param $params Element containing OAuth specific parameters
 : @param $realm Realm that defines the protection space
 : @param $signature string signed signature
 : @return string authorization header
 :)
declare %private function oauth:authorization-header($params as element(p:parameters), $realm as xs:string, $signature as xs:string) as xs:string
{
  concat('OAuth ', 
    string-join(
      (
        concat('realm="', $realm, '"'),
        oauth:normalization($params,'="','"',", "),
        concat('oauth_signature="', $signature, '"')
      ),
      ", ")
  )
};

(:~
 : This function constructs the Signature Base String; a consistent, reproducible concatenation of several of the HTTP request elements into a single string.
 : @see http://tools.ietf.org/html/rfc5849#section-3.4.1
 : @param $http-method string method for posting the request to the url
 : @param $base-uri string base-uri for request, access, or protected resources
 : @param $params Element containing OAuth specific parameters
 : @return signature base string formatted to create the signature
 :)
declare %private function oauth:signature-base-string($http-method as xs:string, $base-uri as xs:string, $params as element(p:parameters)) as xs:string
{
  let $params := validate {
    <p:parameters>{
      for $param in $params/p:parameter
      let $k := $param/@name/string(.)
      let $v := $param/@value/string(.)
      return <p:parameter name="{encode-for-uri($k)}" value="{encode-for-uri($v)}" />
    }</p:parameters>
  }        
  let $base-string := concat($http-method, "&", encode-for-uri($base-uri), "&",
        oauth:normalization($params, "%3D", "%26",""))
  return
    if (true() and matches($base-string, "%26"))
    then trace(replace(trace($base-string, "before"),
           concat('^(.*)', "%26",'.*'),
         '$1'), "after")
    else ''
};

(:~
 : This function generates a signature string which becomes the "oauth_signature" parameter. The service provider verifies the signature as specified for each method.
 : @see http://tools.ietf.org/html/rfc5849#section-3.4
 : @param $base-string signature base string
 : @param $oauth-signature-method string signing method
 : @param $key string signing key
 : @return signature base string formatted to create the signature
 :)
declare %private function oauth:signature($base-string as xs:string, $oauth-signature-method as xs:string, $key as xs:string) as xs:string
{
  (:
   : HMAC-SHA1
   : @see http://tools.ietf.org/html/rfc5849#section-3.4.2
   : uses the hmac-sha1 algorithm found here @see http://tools.ietf.org/html/rfc2104
   :)
  if($oauth-signature-method = "HMAC-SHA1")
  then xs:string(hmac:sha1($base-string, $key))
  (:
   : RSA-SHA1
   : @see http://tools.ietf.org/html/rfc5849#section-3.4.3  
   :)
  else if($oauth-signature-method = "RSA-SHA1")
  then error($oerr:OC001, concat("Method not implemented yet: ", $oauth-signature-method))
  (:
   : PLAINTEXT
   : @see http://tools.ietf.org/html/rfc5849#section-3.4.4 
   :)
  else if($oauth-signature-method = "PLAINTEXT")
  then $key
  else error(xs:QName("oerr:OC001"), concat("Unsupported signing method: ", $oauth-signature-method))
};

(:~
 : This function takes an input string and parses the parameters into parameter elements.
 : @param $input string of parameters to be parsed into element parameters
 : @return element parameters
 :)
declare %private function oauth:parse-parameters($input as xs:string) as element(p:parameters)
{
validate {
 <p:parameters>{
  for $param in fn:tokenize(replace($input, "
",""), "&")
  let $key-value := fn:tokenize($param, "=")
  return <p:parameter name="{$key-value[1]}" value="{$key-value[2]}" />
  }</p:parameters>
}
};

(:~
 : This function makes the request, and sends it to the specified url using the specified http request method.
 :
 : @see http://tools.ietf.org/html/rfc5849#section-3.1
 : @param $consumer-secret Client Shared-Secret, also known as the consumer-secret 
 : @param $method HTTP request method (e.g., "GET", "POST", etc.)
 : @param $oauth-token-secret the temporary credentials shared-secret
 : @param $params Element containing OAuth specific parameters
 : @param $realm Realm that defines the protection space
 : @param $signature-method Method with which the signing key is signed (typically HMAC-SHA1)
 : @param $url Target URL
 : @return HTTP response.
 :)
declare %private %an:sequential function oauth:http-request(
    $consumer-secret as xs:string,
    $protected-resource as element(http:request),
    $oauth-token-secret as xs:string?,
    $params as element(p:parameters),
    $realm as xs:string?,
    $signature-method as xs:string
)
as item()*
{
  let $url    := $protected-resource/@href/string(.)
  let $method := $protected-resource/@method/string(.)
  let $base-url         := trace(tokenize($url, "\?")[1], "base-url")
  let $base-signature   := trace(oauth:signature-base-string($method, $base-url, $params),"base-sign")
  let $key    := trace(oauth:key($consumer-secret, $oauth-token-secret),"sign-key")
  let $signature   := trace(encode-for-uri(oauth:signature($base-signature, $signature-method, $key)),"signature")
  let $authorization-header   := trace(oauth:authorization-header($params,$realm,$signature),"authorization-header")
  let $authorization-header   := <http:header name="Authorization" value="{$authorization-header}" />
  let $request := copy $request := $protected-resource modify (insert node $authorization-header as first into $request) return $request
  return http-client:send-request(
    $request
  )
};

(:~
 : This function creates the parameters to be given to the http-request function. It parses the response into 
 : parameter elements if the response status is 200 (OK).
 :
 : @param $consumer-key Client Identifier, also known as the consumer-key
 : @param $consumer-secret Client Shared-Secret, also known as the consumer-secret
 : @param $method HTTP request method (e.g., "GET", "POST", etc.)
 : @param $oauth-token The temporary credentials identifier
 : @param $oauth-token-secret The temporary credentials shared-secret
 : @param $realm Realm that defines the protection space
 : @param $signature-method Method with which the signing key is signed (typically HMAC-SHA1)
 : @param $url Target URL
 : @param $additional-parameters Parameters specific to a certain step (request-token, authorize, access-token, protected-resource) of the OAuth authorization
 : @return correctly parsed parameters, or an error if http response status is not 200 OK
 : @error XQP0021(oerr:OC003) if we receive http 401 error from the server.
 : @error XQP0021(oerr:OC004) if we receive http 500 error from the server.
 :)
declare %private %an:sequential function oauth:format-request(
    $consumer-key as xs:string,
    $consumer-secret as xs:string,
    $protected-resource as element(http:request),
    $oauth-token as xs:string?,
    $oauth-token-secret as xs:string?,
    $realm as xs:string?,
    $signature-method as xs:string,
    $additional-parameters as element(p:parameters)?,
    $format-params as xs:boolean)
as item()*
{
  let $version := "1.0"
  let $base-params := concat("&oauth_consumer_key=",$consumer-key,"&oauth_nonce=",trace(oauth:nonce(),"nonce"),
"&oauth_signature_method=",$signature-method,"&oauth_timestamp=",oauth:timestamp(),"&oauth_version=",$version)
  let $params := oauth:parse-parameters($base-params)
  let $params := validate { 
    <p:parameters>
    {$params/p:parameter}
    {$additional-parameters/p:parameter}
    </p:parameters>
  }
  let $response := {
    (: Unless the user explicitly sets follow-redirect, we set it to false.
       We have to handle redirects manually to provide a new nonce each time. :)
    if (fn:empty($protected-resource/@follow-redirect))
    then
      insert node attribute { "follow-redirect" } { "false" }
      into $protected-resource;
    else ();
    oauth:http-request($consumer-secret, $protected-resource, $oauth-token-secret, $params, $realm, $signature-method)
  }
  let $head := $response[1]
  let $body := if($response[2] instance of xs:base64Binary) then base64:decode($response[2]) else $response[2]
  let $status := xs:integer($head/@status)
  return
    (: 200, 201, and 202 are normally "OK". 209 is returned by POST requests
       to the Launchpad API; it's not part of the HTTP spec anymore, but we
       need to accept it here if we want to work with Launchpad. :)
    if ( ($status ge 200 and $status le 202) or
         ($status eq 209) )
    then 
      if ($format-params) then
        oauth:parse-parameters($body)
      else 
        $response
    else if ( ($status ge 301 and $status le 303) or 
              ($status eq 307) ) then { 
      replace value of node $protected-resource/@href with
        data($head/http-client:header[@name eq "Location"]/@value);
      oauth:format-request(
        $consumer-key, $consumer-secret, $protected-resource, $oauth-token,
        $oauth-token-secret, $realm, $signature-method, $additional-parameters,
        $format-params)
    }
    else if ($status eq 401) then 
      error(
        xs:QName("oerr:OC003"),
        concat("Authorization header unauthorized: ", $body)
      )
    else
      error($oerr:OC004, concat("Service Provider Error ", $status, ": ", $body))

};

declare %private %an:sequential function oauth:format-request(
    $consumer-key as xs:string,
    $consumer-secret as xs:string,
    $protected-resource as element(http:request),
    $oauth-token as xs:string?,
    $oauth-token-secret as xs:string?,
    $realm as xs:string?,
    $signature-method as xs:string,
    $additional-parameters as element(p:parameters)?)
as item()*
{
  oauth:format-request($consumer-key, $consumer-secret, $protected-resource, $oauth-token, $oauth-token-secret, $realm, $signature-method, $additional-parameters, true())
};

(:~
 : This function finds the server-specific parameters. The specification states, "Servers MAY specify additional parameters..."
 : @see http://tools.ietf.org/html/rfc5849#section-2.1
 : @param $request element containing the client's request
 : @return may return parsed parameters or empty sequence if there were no server specific parameters
 :)
declare %private function oauth:additional-parameters($parameters as element(p:parameters)?) as element(p:parameters)?
{
  let $additional-parameters := validate {
    <p:parameters>{
    for $param in $parameters/p:parameter
    let $key := $param/@name/string(.)
    let $value := $param/@value/string(.)
    where (($key != "oauth_callback")         and 
           ($key != "oauth_consumer_key")     and 
           ($key != "oauth_consumer_secret")  and 
           ($key != "oauth_signature_method") and 
           ($key != "method")                 and 
           ($key != "realm")                  and
           ($key != "url")                    and
           ($key != "oauth_token")            and
           ($key != "oauth_token_secret")     and
           ($key != "oauth_verifier"))
    return $param
    }</p:parameters>
  }
  let $parameters :=
    if(empty($additional-parameters/p:parameter))
    then (:"":) ()
    else $additional-parameters(:oauth:parse-parameters(concat("&", oauth:normalization($additional-parameters,'=',(),"&"))):)
  return $parameters
};

(:~
 : This function allows the client to obtain a set of temporary credentials from the service provider by making an authenticated HTTP request to the Temporary Credential Request endpoint.
 :
 : @see http://tools.ietf.org/html/rfc5849#section-2.1
 : @param $consumer-key Client Identifier, also known as the consumer-key
 : @param $consumer-secret Client Shared-Secret, also known as the consumer-secret
 : @param $signature-method Method with which the signing key is signed (typically HMAC-SHA1)
 : @param $method HTTP request method (e.g., "GET", "POST", etc.) HTTP request method
 : @param $realm Realm that defines the protection space
 : @param $temporary-credential-request Target url for temporary credentials request
 : @param $callback-url Service provider redirects the user to this url after authorization. The parameter value MUST be set to "oob" (case sensitive), to indicate an out-of-band configuration.
 : @param $additional-parameters Parameters specific to a certain step (request-token) of the OAuth authorization
 : @return temporary credentials correctly parsed as parameter elements, or an error if http response status is not 200 OK
 :)
declare %private %an:sequential %an:nondeterministic function oauth:request-token(
    $consumer-key as xs:string,
    $consumer-secret as xs:string,
    $signature-method as xs:string,
    $method as xs:string,
    $realm as xs:string,
    $temporary-credential-request as xs:string,
    $callback-url as xs:string,
    $additional-parameters as element(p:parameters)?) as element(p:parameters)
{
  let $protected-resource := validate {
    <http:request href="{$temporary-credential-request}" method="{$method}" />
  }
  let $add := oauth:add-parameter($additional-parameters, "oauth_callback", encode-for-uri($callback-url))
  return oauth:format-request($consumer-key,$consumer-secret, $protected-resource,(),(),$realm,$signature-method,$add)
};

(:~
 : This function allows the client to obtain a set of token credentials from the service provider by making an authenticated HTTP request to the Token Request endpoint.
 :
 : @see http://tools.ietf.org/html/rfc5849#section-2.3
 : @param $consumer-key Client Identifier, also known as the consumer-key
 : @param $consumer-secret Client Shared-Secret, also known as the consumer-secret
 : @param $signature-method Method with which the signing key is signed (typically HMAC-SHA1)
 : @param $method HTTP request method (e.g., "GET", "POST", etc.)
 : @param $realm Realm that defines the protection space
 : @param $token-request-uri Target uri for token credentials request
 : @param $oauth-token The temporary credentials identifier
 : @param $oauth-token-secret the temporary credentials shared-secret
 : @param $additional-parameters Parameters specific to a certain step (access-token) of the OAuth authorization
 : @return token credentials correctly parsed as parameter elements, or an error if http response status is not 200 OK
 :)
declare %private %an:sequential %an:nondeterministic function oauth:access-token(
    $consumer-key as xs:string,
    $consumer-secret as xs:string,
    $signature-method as xs:string,
    $realm as xs:string,
    $oauth-token as xs:string,
    $oauth-token-secret as xs:string,
    $method as xs:string,
    $token-request-uri as xs:string,
    $additional-parameters as element(p:parameters)?)
as element(p:parameters)
{
  let $protected-resource := validate {
    <http:request href="{$token-request-uri}" method="{$method}" />
  }
  let $additional-parameters := oauth:add-parameter($additional-parameters, "oauth_token", $oauth-token)
  return
  oauth:format-request($consumer-key,$consumer-secret,$protected-resource,$oauth-token,$oauth-token-secret,$realm,$signature-method, $additional-parameters)
};

(:~
 : This function allows the client access to the protected resources of the user.
 :
 : @see http://tools.ietf.org/html/rfc5849#section-3
 : @param $consumer-key Client Identifier, also known as the consumer-key
 : @param $consumer-secret Client Shared-Secret, also known as the consumer-secret
 : @param $signature-method Method with which the signing key is signed (typically HMAC-SHA1)
 : @param $method HTTP request method (e.g., "GET", "POST", etc.)
 : @param $realm Realm that defines the protection space
 : @param $url Target URL
 : @param $oauth-token The temporary credentials identifier
 : @param $oauth-token-secret the temporary credentials shared-secret
 : @param $additional-parameters Parameters specific to a certain step (protected-resource) of the OAuth authorization
 : @return protected resources parsed as parameter elements, or an error if http response status is not 200 OK
 :)
declare %private %an:sequential %an:nondeterministic function oauth:protected-resource(
    $consumer-key as xs:string,
    $consumer-secret as xs:string,
    $signature-method as xs:string,
    $oauth-token as xs:string,
    $oauth-token-secret as xs:string,
    $realm as xs:string,
    $protected-resource as schema-element(http:request),
    $additional-parameters as element(p:parameters)?)
as item()*
{
  let $parameters := oauth:add-parameter($additional-parameters, "oauth_token", $oauth-token)
  return oauth:format-request($consumer-key,$consumer-secret, $protected-resource, $oauth-token,$oauth-token-secret,$realm,$signature-method,$parameters, false())
};