78253e2015-10-12Martin Nilsson #pike __REAL_VERSION__
40a21e2015-10-09Pontus Östlund //! OAuth module //! //! @b{Example@} //! //! @code
56efed2015-10-13Martin Nilsson //! import Web.Auth.OAuth;
40a21e2015-10-09Pontus Östlund //! //! string endpoint = "http://twitter.com/users/show.xml"; //! //! Consumer consumer = Consumer(my_consumer_key, my_consumer_secret); //! Token token = Token(my_access_token_key, my_access_token_secret); //! Params params = Params(Param("user_id", 12345)); //! Request request = request(consumer, token, params); //! //! request->sign_request(Signature.HMAC_SHA1, consumer, token); //! Protocols.HTTP.Query query = request->submit(); //! //! if (query->status != 200) //! error("Bad response status: %d\n", query->status); //! //! werror("Data is: %s\n", query->data()); //! @endcode //! Verion constant VERSION = "1.0";
3332892015-10-13Martin Nilsson //! Query string variable name for the consumer key.
40a21e2015-10-09Pontus Östlund constant CONSUMER_KEY_KEY = "oauth_consumer_key";
3332892015-10-13Martin Nilsson //! Query string variable name for a callback URL.
40a21e2015-10-09Pontus Östlund constant CALLBACK_KEY = "oauth_callback";
3332892015-10-13Martin Nilsson //! Query string variable name for the version.
40a21e2015-10-09Pontus Östlund constant VERSION_KEY = "oauth_version";
3332892015-10-13Martin Nilsson //! Query string variable name for the signature method.
40a21e2015-10-09Pontus Östlund constant SIGNATURE_METHOD_KEY = "oauth_signature_method";
3332892015-10-13Martin Nilsson //! Query string variable name for the signature.
40a21e2015-10-09Pontus Östlund constant SIGNATURE_KEY = "oauth_signature";
3332892015-10-13Martin Nilsson //! Query string variable name for the timestamp.
40a21e2015-10-09Pontus Östlund constant TIMESTAMP_KEY = "oauth_timestamp";
3332892015-10-13Martin Nilsson //! Query string variable name for the nonce.
40a21e2015-10-09Pontus Östlund constant NONCE_KEY = "oauth_nonce";
3332892015-10-13Martin Nilsson //! Query string variable name for the token key.
40a21e2015-10-09Pontus Östlund constant TOKEN_KEY = "oauth_token";
3332892015-10-13Martin Nilsson //! Query string variable name for the token secret.
40a21e2015-10-09Pontus Östlund constant TOKEN_SECRET_KEY = "oauth_token_secret"; #include "oauth.h"
3332892015-10-13Martin Nilsson //! Helper method to create a @[Request] object.
40a21e2015-10-09Pontus Östlund //! //! @throws
3332892015-10-13Martin Nilsson //! An error if @[consumer] is null.
40a21e2015-10-09Pontus Östlund //! //! @param http_method
3332892015-10-13Martin Nilsson //! Defaults to GET.
40a21e2015-10-09Pontus Östlund Request request(string|Standards.URI uri, Consumer consumer, Token token, void|Params params, void|string http_method) { if (!consumer) ARG_ERROR("consumer", "Can not be NULL."); TRACE("### Token: %O\n", token); Params dparams = get_default_params(consumer, token); if (params) dparams += params; return Request(uri, http_method||"GET", dparams); }
3332892015-10-13Martin Nilsson //! Returns the default params for authentication/signing.
40a21e2015-10-09Pontus Östlund Params get_default_params(Consumer consumer, Token token) { Params p = Params( Param(VERSION_KEY, VERSION), Param(NONCE_KEY, nonce()), Param(TIMESTAMP_KEY, time(1)), Param(CONSUMER_KEY_KEY, consumer->key) ); if (token && token->key) p += Param(TOKEN_KEY, token->key); return p; } //! Converts a query string, or a mapping, into a @[Params] object. Params query_to_params(string|Standards.URI|mapping q) { if (objectp(q)) q = (string)q; Params ret = Params(); if (!q || !sizeof(q)) return ret; if (mappingp(q)) { foreach(q; string n; string v) ret += Param(n, v); return ret; } int pos = 0, len = sizeof(q); if ((pos = search(q, "?")) > -1) q = ([string]q)[pos+1..]; foreach (q/"&", string p) { sscanf(p, "%s=%s", string n, string v); if (n && v) ret += Param(n, v); } return ret; }
3332892015-10-13Martin Nilsson //! Class for building a signed request and querying the remote //! service.
40a21e2015-10-09Pontus Östlund class Request {
3332892015-10-13Martin Nilsson  //! The remote endpoint.
40a21e2015-10-09Pontus Östlund  protected Standards.URI uri;
3332892015-10-13Martin Nilsson  //! The signature basestring.
40a21e2015-10-09Pontus Östlund  protected string base_string;
3332892015-10-13Martin Nilsson  //! String representation of the HTTP method.
40a21e2015-10-09Pontus Östlund  protected string method;
3332892015-10-13Martin Nilsson  //! The parameters to send.
40a21e2015-10-09Pontus Östlund  protected Params params;
3332892015-10-13Martin Nilsson  //! Creates a new @[Request.]
40a21e2015-10-09Pontus Östlund  //! //! @seealso //! @[request()] //!
93a5b02015-10-12Martin Nilsson  //! @param uri
3332892015-10-13Martin Nilsson  //! The uri to request.
93a5b02015-10-12Martin Nilsson  //! @param http_method
3332892015-10-13Martin Nilsson  //! The HTTP method to use. Either @expr{"GET"@} or @expr{"POST"@}.
93a5b02015-10-12Martin Nilsson  protected void create(string|Standards.URI uri, string http_method, void|Params params)
40a21e2015-10-09Pontus Östlund  {
ca3b772015-10-12Henrik Grubbström (Grubba)  this::uri = ASSURE_URI(uri);
40a21e2015-10-09Pontus Östlund  method = upper_case(http_method);
ca3b772015-10-12Henrik Grubbström (Grubba)  this::params = query_to_params(uri);
40a21e2015-10-09Pontus Östlund 
93a5b02015-10-12Martin Nilsson  if (params) add_params(params);
40a21e2015-10-09Pontus Östlund  if (!(< "GET", "POST" >)[method]) ARG_ERROR("http_method", "Must be one of \"GET\" or \"POST\"."); }
3332892015-10-13Martin Nilsson  //! Add a param.
40a21e2015-10-09Pontus Östlund  //! //! @returns
3332892015-10-13Martin Nilsson  //! The object being called. this_program add_param(Param|string name, void|string value)
40a21e2015-10-09Pontus Östlund  { if (objectp(name)) params += name; else params += Param(name, value);
3332892015-10-13Martin Nilsson  return this;
40a21e2015-10-09Pontus Östlund  } //! Add a @[Params] object.
93a5b02015-10-12Martin Nilsson  void add_params(Params params)
40a21e2015-10-09Pontus Östlund  {
ca3b772015-10-12Henrik Grubbström (Grubba)  this::params += params;
40a21e2015-10-09Pontus Östlund  }
3332892015-10-13Martin Nilsson  //! Get param with name @[name].
40a21e2015-10-09Pontus Östlund  Param get_param(string name) { foreach (values(params), Param p) if (p[name]) return p; return 0; }
3332892015-10-13Martin Nilsson  //! Returns the @[Params] collection.
40a21e2015-10-09Pontus Östlund  Params get_params() { return params; }
3332892015-10-13Martin Nilsson  //! Signs the request.
40a21e2015-10-09Pontus Östlund  //! //! @param signature_type
3332892015-10-13Martin Nilsson  //! One of the types in @[Signature].
40a21e2015-10-09Pontus Östlund  void sign_request(int signature_type, Consumer consumer, Token token) { object sig = .Signature.get_object(signature_type); params += Param(SIGNATURE_METHOD_KEY, sig->get_method()); params += Param(SIGNATURE_KEY, sig->build_signature(this, consumer, token)); }
3332892015-10-13Martin Nilsson  //! Generates a signature base.
40a21e2015-10-09Pontus Östlund  string get_signature_base() { TRACE("+++ get_signature_base(%s, %s, %s)\n\n", method, (normalize_uri(uri)), (params->get_signature())); return ({ method,
bdb0af2015-10-12Martin Nilsson  Protocols.HTTP.uri_encode(normalize_uri(uri)), Protocols.HTTP.uri_encode(params->get_signature())
40a21e2015-10-09Pontus Östlund  }) * "&"; }
3332892015-10-13Martin Nilsson  //! Send the request to the remote endpoint.
40a21e2015-10-09Pontus Östlund  Protocols.HTTP.Query submit(void|mapping extra_headers) { mapping args = params->get_variables(); foreach (args; string k; string v) { if (!v) continue; if (String.width(v) == 8) catch(args[k] = utf8_to_string(v)); } if (!extra_headers) extra_headers = ([]); string realm = uri->scheme + "://" + uri->host; extra_headers["Authorization"] = "OAuth realm=\"" + realm + "\"," + params->get_auth_header(); extra_headers["Content-Type"] = "text/plain; charset=utf-8"; TRACE("submit(%O, %O, %O, %O)\n", method, uri, args, extra_headers); return Protocols.HTTP.do_method(method, uri, args, extra_headers); }
3332892015-10-13Martin Nilsson  //! It is possible to case the Request object to a string, which //! will be the request URL.
029b5b2015-10-12Martin Nilsson  protected string cast(string how)
40a21e2015-10-09Pontus Östlund  { if (how != "string") { ARG_ERROR("how", "%O can not be casted to \"%s\", only to \"string\"\n", this, how); } return (method == "GET" ? normalize_uri(uri) + "?" : "")+(string)params; }
029b5b2015-10-12Martin Nilsson  protected string _sprintf(int t)
40a21e2015-10-09Pontus Östlund  { return t == 'O' && sprintf("%O(%O, %O, %O)", object_program(this), (string)uri, base_string, params); } } //! An OAuth user class Consumer { //! Consumer key string key; //! Consumer secret string secret; //! Callback url that the remote verifying page will return to. string|Standards.URI callback;
3332892015-10-13Martin Nilsson  //! Creates a new @[Consumer] object.
40a21e2015-10-09Pontus Östlund  //!
93a5b02015-10-12Martin Nilsson  //! @param callback
3332892015-10-13Martin Nilsson  //! NOTE: Has no effect in this implementation.
93a5b02015-10-12Martin Nilsson  protected void create(string key, string secret, void|string|Standards.URI callback)
40a21e2015-10-09Pontus Östlund  {
ca3b772015-10-12Henrik Grubbström (Grubba)  this::key = key; this::secret = secret; this::callback = ASSURE_URI(callback);
40a21e2015-10-09Pontus Östlund  }
029b5b2015-10-12Martin Nilsson  protected string _sprintf(int t)
40a21e2015-10-09Pontus Östlund  { return t == 'O' && sprintf("%O(%O, %O, %O)", object_program(this), key, secret, callback); } } //! Token class.
93a5b02015-10-12Martin Nilsson class Token (string key, string secret)
40a21e2015-10-09Pontus Östlund {
3332892015-10-13Martin Nilsson  //! Only supports casting to string wich will return a query string //! of the object.
029b5b2015-10-12Martin Nilsson  protected string cast(string how)
40a21e2015-10-09Pontus Östlund  { switch (how) { case "string": return "oauth_token=" + key + "&" "oauth_token_secret=" + secret; } error("Can't cast %O() to %O\n", object_program(this), how); }
029b5b2015-10-12Martin Nilsson  protected string _sprintf(int t)
40a21e2015-10-09Pontus Östlund  { return t == 'O' && sprintf("%O(%O, %O)", object_program(this), key, secret); } }
3332892015-10-13Martin Nilsson //! Represents a query string parameter, i.e. @tt{key=value@}.
40a21e2015-10-09Pontus Östlund class Param { //! Param name protected string name; //! Param value protected string value; protected int(0..1) is_null = 1;
3332892015-10-13Martin Nilsson  //! Creates a new @[Param].
93a5b02015-10-12Martin Nilsson  protected void create(string name, mixed value)
40a21e2015-10-09Pontus Östlund  {
ca3b772015-10-12Henrik Grubbström (Grubba)  this::name = name;
93a5b02015-10-12Martin Nilsson  set_value(value);
40a21e2015-10-09Pontus Östlund  }
3332892015-10-13Martin Nilsson  //! Getter for the name attribute.
40a21e2015-10-09Pontus Östlund  string get_name() { return name; }
3332892015-10-13Martin Nilsson  //! Setter for the value attribute.
40a21e2015-10-09Pontus Östlund  void set_name(string value) { name = value; }
3332892015-10-13Martin Nilsson  //! Getter for the value attribute.
40a21e2015-10-09Pontus Östlund  string get_value() { return value; }
3332892015-10-13Martin Nilsson  //! Setter for the value attribute.
40a21e2015-10-09Pontus Östlund  void set_value(mixed _value) { value = (string)_value;
93a5b02015-10-12Martin Nilsson  is_null = !_value;
40a21e2015-10-09Pontus Östlund  }
3332892015-10-13Martin Nilsson  //! Returns the value encoded.
93a5b02015-10-12Martin Nilsson  string get_encoded_value() { return value && Protocols.HTTP.uri_encode(value); }
40a21e2015-10-09Pontus Östlund 
3332892015-10-13Martin Nilsson  //! Returns the name and value for usage in a signature string.
40a21e2015-10-09Pontus Östlund  string get_signature() {
93a5b02015-10-12Martin Nilsson  return name && value && Protocols.HTTP.uri_encode(name) + "=" + Protocols.HTTP.uri_encode(value);
40a21e2015-10-09Pontus Östlund  }
3332892015-10-13Martin Nilsson  //! Comparer method. Checks if @[other] equals this object.
029b5b2015-10-12Martin Nilsson  protected int(0..1) `==(mixed other)
40a21e2015-10-09Pontus Östlund  { if (object_program(other) != Param) return 0; if (name == other->get_name()) return value == other->get_value(); return 0; }
3332892015-10-13Martin Nilsson  //! Checks if this object is greater than @[other].
029b5b2015-10-12Martin Nilsson  protected int(0..1) `>(mixed other)
40a21e2015-10-09Pontus Östlund  { if (object_program(other) != Param) return 0; if (name == other->get_name()) return value > other->get_value(); return name > other->get_name(); }
3332892015-10-13Martin Nilsson  //! Index lookup.
029b5b2015-10-12Martin Nilsson  protected object `[](string key)
40a21e2015-10-09Pontus Östlund  { if (key == name) return this; return 0; }
029b5b2015-10-12Martin Nilsson  protected string _sprintf(int t)
40a21e2015-10-09Pontus Östlund  { return t == 'O' && sprintf("%O(%O, %O)", object_program(this), name, value); } } //! Collection of @[Param] class Params {
3332892015-10-13Martin Nilsson  //! Storage for @[Param]s of this object.
40a21e2015-10-09Pontus Östlund  private array(Param) params; //! Create a new @[Params] //!
93a5b02015-10-12Martin Nilsson  //! @param params
3332892015-10-13Martin Nilsson  //! Arbitrary number of @[Param] objects.
93a5b02015-10-12Martin Nilsson  protected void create(Param ... params)
40a21e2015-10-09Pontus Östlund  {
ca3b772015-10-12Henrik Grubbström (Grubba)  this::params = params||({});
40a21e2015-10-09Pontus Östlund  }
3332892015-10-13Martin Nilsson  //! Returns the params for usage in an authentication header.
40a21e2015-10-09Pontus Östlund  string get_auth_header() { array a = ({}); foreach (params, Param p) { if (has_prefix(p->get_name(), "oauth_")) { if (p->get_value()) a += ({ p->get_name() + "=\"" + p->get_encoded_value() + "\"" }); } else { TRACE("**** Some shitty param: %O\n", p); } } return a*","; }
3332892015-10-13Martin Nilsson  //! Returns the parameters as a mapping.
40a21e2015-10-09Pontus Östlund  mapping get_variables() { mapping m = ([]); foreach (params, Param p) { if (!has_prefix(p->get_name(), "oauth_")) m[p->get_name()] = p->get_value(); } return m; }
3332892015-10-13Martin Nilsson  //! Returns the parameters as a query string.
40a21e2015-10-09Pontus Östlund  string get_query_string() { array s = ({}); foreach (params, Param p) if (!has_prefix(p->get_name(), "oauth_"))
bdb0af2015-10-12Martin Nilsson  s += ({ p->get_name() + "=" + Protocols.HTTP.uri_encode(p->get_value()) });
40a21e2015-10-09Pontus Östlund  return s*"&"; }
3332892015-10-13Martin Nilsson  //! Returns the parameters as a mapping with encoded values.
40a21e2015-10-09Pontus Östlund  //! //! @seealso //! @[get_variables()] mapping get_encoded_variables() { mapping m = ([]); foreach (params, Param p) if (!has_prefix(p->get_name(), "oauth_"))
bdb0af2015-10-12Martin Nilsson  m[p->get_name()] = Protocols.HTTP.uri_encode(p->get_value());
40a21e2015-10-09Pontus Östlund  return m; }
3332892015-10-13Martin Nilsson  //! Returns the parameters for usage in a signature base string.
40a21e2015-10-09Pontus Östlund  string get_signature() { return ((sort(params)->get_signature()) - ({ 0 })) * "&"; }
3332892015-10-13Martin Nilsson  //! Supports casting to @tt{mapping@}, which will map parameter //! names to their values.
029b5b2015-10-12Martin Nilsson  protected mapping cast(string how)
40a21e2015-10-09Pontus Östlund  { switch (how) { case "mapping": mapping m = ([]); foreach (params, Param p) m[p->get_name()] = p->get_value(); return m; break; } }
3332892015-10-13Martin Nilsson  //! Append mapping @[args] as @[Param] objects.
40a21e2015-10-09Pontus Östlund  //! //! @returns
3332892015-10-13Martin Nilsson  //! The object being called.
029b5b2015-10-12Martin Nilsson  this_program add_mapping(mapping args)
40a21e2015-10-09Pontus Östlund  { foreach (args; string k; string v) params += ({ Param(k, v) }); return this_object; }
3332892015-10-13Martin Nilsson  //! Append @[p] to the internal array.
40a21e2015-10-09Pontus Östlund  //! //! @returns
3332892015-10-13Martin Nilsson  //! The object being called.
029b5b2015-10-12Martin Nilsson  protected this_program `+(Param|Params p)
40a21e2015-10-09Pontus Östlund  { params += object_program(p) == Params ? values(p) : ({ p }); return this_object(); }
3332892015-10-13Martin Nilsson  //! Removes @[p] from the internal array.
40a21e2015-10-09Pontus Östlund  //! //! @returns
3332892015-10-13Martin Nilsson  //! The object being called.
029b5b2015-10-12Martin Nilsson  protected this_program `-(Param p)
40a21e2015-10-09Pontus Östlund  { foreach (params, Param pm) { if (pm == p) { params -= ({ pm }); break; } } return this_object(); } //! Index lookup //! //! @returns //! If no @[Param] is found returns @tt{0@}. //! If multiple @[Param]s with name @[key] is found a new @[Params] object //! with the found params will be retured. //! If only one @[Param] is found that param will be returned.
029b5b2015-10-12Martin Nilsson  protected mixed `[](string key)
40a21e2015-10-09Pontus Östlund  { array(Param) p = params[key]-({0}); if (!p) return 0; return sizeof(p) == 1 ? p[0] : Params(@p); }
3332892015-10-13Martin Nilsson  //! Returns the @[params].
029b5b2015-10-12Martin Nilsson  protected mixed _values()
40a21e2015-10-09Pontus Östlund  { sort(params); return params; }
3332892015-10-13Martin Nilsson  //! Returns the size of the @[params] array. protected int(0..) _sizeof()
40a21e2015-10-09Pontus Östlund  { return sizeof(params); }
029b5b2015-10-12Martin Nilsson  protected string _sprintf(int t)
40a21e2015-10-09Pontus Östlund  { return t == 'O' && sprintf("%O(%O)", object_program(this), params); } } //! Normalizes @[uri] //! //! @param uri
3332892015-10-13Martin Nilsson //! A @tt{string@} or @[Standards.URI].
40a21e2015-10-09Pontus Östlund string normalize_uri(string|Standards.URI uri) { uri = ASSURE_URI(uri); string nuri = sprintf("%s://%s", uri->scheme, uri->host); if (!(<"http","https">)[uri->scheme] || !(<80,443>)[uri->port]) nuri += ":" + uri->port; return nuri + uri->path; }
3332892015-10-13Martin Nilsson //! Generates a @tt{nonce@}.
40a21e2015-10-09Pontus Östlund string nonce() {
67fa1c2015-10-13Martin Nilsson  return MIME.encode_base64(random_string(15),1);
40a21e2015-10-09Pontus Östlund }