pike.git
/
lib
/
modules
/
Auth.pmod
/
OAuth.pmod
/
Authentication.pike
version
»
Context lines:
10
20
40
80
file
none
3
pike.git/lib/modules/Auth.pmod/OAuth.pmod/Authentication.pike:1:
+
/*
+
Author: Pontus Östlund <https://profiles.google.com/poppanator>
-
+
Permission to copy, modify, and distribute this source for any legal
+
purpose granted as long as my name is still attached to it. More
+
specifically, the GPL, LGPL and MPL licenses apply to this software.
+
*/
+
+
//! The purpose of this class is to streamline OAuth1 with OAuth2.
+
//! This class will not do much on it's own, since its purpose is to
+
//! be inherited by some other class implementing a specific authorization
+
//! service.
+
+
inherit Auth.OAuth2.Client : oauth2;
+
inherit .Client : oauth;
+
+
#include "oauth.h"
+
+
//! The endpoint to send request for a request token
+
constant REQUEST_TOKEN_URL = 0;
+
+
//! The endpoint to send request for an access token
+
constant ACCESS_TOKEN_URL = 0;
+
+
//! The enpoint to redirect to when authorize an application
+
constant USER_AUTH_URL = 0;
+
+
/*
+
#ifdef OAUTH_DEBUG
+
#define TRACE(X...)werror("%s:%d: %s",basename(__FILE__),__LINE__,sprintf(X))
+
#else
+
#define TRACE(X...)0
+
#endif
+
*/
+
+
//! Creates an OAuth object
+
//!
+
//! @param client_id
+
//! The application ID
+
//!
+
//! @param client_secret
+
//! The application secret
+
//!
+
//! @param redirect_uri
+
//! Where the authorization page should redirect back to. This must be a
+
//! fully qualified domain name. This can be set/overridden in
+
//! @[get_request_token()].
+
//!
+
//! @param scope
+
//! Extended permissions to use for this authentication. This can be
+
//! set/overridden in @[get_auth_uri()].
+
void create(string client_id, string client_secret, void|string redir,
+
void|string|array(string)|multiset(string) scope)
+
{
+
oauth2::create(client_id, client_secret, redir, scope);
+
oauth::create(.Consumer(client_id, client_secret),
+
.Token(0, 0));
+
}
+
+
//! Set authentication
+
//!
+
//! @param key
+
//!
+
void set_authentication(string key, void|string secret)
+
{
+
token->key = key;
+
token->secret = secret;
+
oauth2::access_token = key;
+
}
+
+
int(0..1) is_authenticated()
+
{
+
return !!token->key && !!token->secret && !oauth2::is_expired();
+
}
+
+
//! Populate this object with the result from
+
//! @[request_access_token()].
+
//!
+
//! @param encoded_value
+
//!
+
//! @returns
+
//! The object being called
+
this_program set_from_cookie(string encoded_value)
+
{
+
mixed e = catch {
+
oauth2::set_from_cookie(encoded_value);
+
if (gettable->oauth_token) {
+
oauth2::access_token = gettable->oauth_token;
+
gettable->access_token = oauth2::access_token;
+
}
+
token = .Token(gettable->oauth_token, gettable->oauth_token_secret);
+
return this;
+
};
+
+
error("Unable to decode cookie! %s. ", describe_error(e));
+
}
+
+
//! Fetches a request token
+
//!
+
//! @param callback_uri
+
//! Overrides the callback uri in the application settings
+
//! @param force_login
+
//! If @tt{1@} forces the user to provide its credentials at the Twitter
+
//! login page.
+
.Token get_request_token(void|string|Standards.URI callback_uri,
+
void|int(0..1) force_login)
+
{
+
mapping p = ([]);
+
+
if (callback_uri)
+
p->oauth_callback = (string)callback_uri;
+
+
if (force_login)
+
p->force_login = "true";
+
+
TRACE("OAuth1: get_request_token(%O, %O)\n", REQUEST_TOKEN_URL, p);
+
+
string ctoken = call(REQUEST_TOKEN_URL, p, "POST");
+
mapping res = ctoken && (mapping).query_to_params(ctoken);
+
+
TRACE("OAuth1: get_request_token result: %O\n", res);
+
+
token = .Token(res[.TOKEN_KEY],
+
res[.TOKEN_SECRET_KEY]);
+
return token;
+
}
+
+
//! Fetches an access token
+
//!
+
//! @param oauth_verifier
+
protected string low_get_access_token(void|string oauth_verifier)
+
{
+
if (!token)
+
error("Can't fetch access token when no request token is set!\n");
+
+
.Params pm;
+
+
if (oauth_verifier) {
+
pm = .Params(.Param("oauth_verifier", oauth_verifier));
+
}
+
+
TRACE("OAuth1: get_access_token(%O, %O)\n", ACCESS_TOKEN_URL, pm);
+
+
string ctoken = call(ACCESS_TOKEN_URL, pm, "POST");
+
return ctoken;
+
}
+
+
//! Fetches an access token
+
//!
+
//! @param oauth_verifier
+
.Token get_access_token(void|string oauth_verifier)
+
{
+
string ctoken = low_get_access_token(oauth_verifier);
+
mapping p = (mapping).query_to_params(ctoken);
+
+
token = .Token(p[.TOKEN_KEY],
+
p[.TOKEN_SECRET_KEY]);
+
+
return token;
+
}
+
+
//! Same as @[get_access_token] except this returns a string
+
//! to comply with the OAuth2 authentication process
+
string request_access_token(string code)
+
{
+
string ctoken = low_get_access_token(code);
+
mapping p = (mapping).query_to_params(ctoken);
+
+
token = .Token(p[.TOKEN_KEY],
+
p[.TOKEN_SECRET_KEY]);
+
+
return encode_value(p);
+
}
+
+
string get_auth_uri(void|mapping args)
+
{
+
if (!args) args = ([]);
+
get_request_token(args->callback_uri||oauth2::_redirect_uri,
+
args->force_login);
+
return sprintf("%s?%s=%s", USER_AUTH_URL, .TOKEN_KEY,
+
(token && token->key)||"");
+
}
+
+
//! Does the low level HTTP call to Twitter.
+
//!
+
//! @throws
+
//! An error if HTTP status != 200
+
//!
+
//! @param url
+
//! The full address to the Twitter service e.g:
+
//! @tt{http://twitter.com/direct_messages.xml@}
+
//! @param args
+
//! Arguments to send with the request
+
//! @param mehod
+
//! The HTTP method to use
+
string call(string|Standards.URI url, void|mapping|.Params args,
+
void|string method)
+
{
+
method = normalize_method(method);
+
+
if (mappingp(args)) {
+
mapping m = copy_value(args);
+
args = .Params();
+
args->add_mapping(m);
+
}
+
+
TRACE("Token: %O\n", token);
+
TRACE("Consumer: %O\n", consumer);
+
TRACE("Args: %O\n", args);
+
TRACE("Method: %O\n", method);
+
+
.Request r = .request(url, consumer, token, args, method);
+
r->sign_request(.Signature.HMAC_SHA1, consumer, token);
+
+
Protocols.HTTP.Query q = r->submit();
+
+
TRACE("Auth.OAuth.Authentication()->call(%O) : %O : %s\n",
+
q, q->headers, q->data());
+
+
if (q->status != 200) {
+
if (mapping e = parse_error_xml(q->data()))
+
error("Error in %O: %s\n", e->request, e->error);
+
else
+
error("Bad status, %d, from HTTP query!\n", q->status);
+
}
+
+
return q->data();
+
}
+
+
//! Normalizes and verifies the HTTP method to be used in a HTTP call
+
//!
+
//! @param method
+
protected string normalize_method(string method)
+
{
+
method = upper_case(method||"GET");
+
if (!(< "GET", "POST", "DELETE", "PUT", "HEAD", "PATCH" >)[method])
+
error("HTTP method must be GET, POST, PUT, HEAD, PATCH or DELETE! ");
+
+
return method;
+
}
+
+
import Parser.XML.Tree;
+
+
//! Parses an error xml tree
+
//!
+
//! @param xml
+
//!
+
//! @returns
+
//! A mapping:
+
//! @mapping
+
//! @member string "request"
+
//! @member string "error"
+
//! @endmapping
+
mapping parse_error_xml(string xml)
+
{
+
mapping m;
+
if (Node n = get_xml_root(xml)) {
+
m = ([]);
+
foreach (n->get_children(), Node cn) {
+
if (cn->get_node_type() == XML_ELEMENT)
+
m[cn->get_tag_name()] = cn->value_of_node();
+
}
+
}
+
+
return m;
+
}
+
+
//! Returns the first @tt{XML_ELEMENT@} node in an XML tree.
+
//!
+
//! @param xml
+
//! Either an XML tree as a string or a node object.
+
private Node get_xml_root(string|Node xml)
+
{
+
catch {
+
if (stringp(xml))
+
xml = parse_input(xml);
+
+
foreach (xml->get_children(), Node n) {
+
if (n->get_node_type() == XML_ELEMENT) {
+
xml = n;
+
break;
+
}
+
}
+
+
return objectp(xml) && xml;
+
};
+
}
Newline at end of file added.