Skip to content

Instantly share code, notes, and snippets.

@vdemay
Created December 27, 2011 13:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save vdemay/1523625 to your computer and use it in GitHub Desktop.
Save vdemay/1523625 to your computer and use it in GitHub Desktop.
Scoop.it OAuth
<?php
include("scoop.php");
session_start();
$consumerKey = $_POST['consumerkey'];
if($consumerKey==null){
$consumerKey = $_SESSION['consumerkey'];
} else {
$_SESSION['consumerkey'] = $consumerKey;
}
$consumerSecret = $_POST['consumersecret'];
if($consumerSecret==null){
$consumerSecret = $_SESSION['consumersecret'];
} else {
$_SESSION['consumersecret'] = $consumerSecret;
}
$scoopUrl = $_POST['scoopurl'];
if($scoopUrl==null){
$scoopUrl = $_SESSION['scoopurl'];
} else {
$_SESSION['scoopurl'] = $scoopUrl;
}
$localUrl = $_POST['localurl'];
if($localUrl==null){
$localUrl = $_SESSION['localurl'];
} else {
$_SESSION['localurl'] = $localUrl;
}
$scoop==null;
// Do OAUTH authentication
if($consumerKey!=null && $consumerSecret !=null && $scoopUrl!=null && $localUrl!=null){
try {
$scoop = new Scoop(new SessionTokenStore(), $localUrl,$consumerKey,$consumerSecret,$scoopUrl);
} catch(Exception $e){
print_r($e);
$scoop=null;
}
}
$getResult=null;
// do get.it!
if($_POST['submit']=='Get.it!' && $scoop!=null){
try {
$url = $_POST['url'];
$queryString = $_POST['queryString'];
$getResult = $scoop->get($scoopUrl.'api/1/'.$url.'?'.$queryString);
} catch(Exception $e){
print_r($e);
}
}
$postResult=null;
if($_POST['submit']=='Post.it!' && $scoop!=null){
try {
$postUrl = $_POST['postUrl'];
$postQueryString = $_POST['postQueryString'];
$postResult = $scoop->post($scoopUrl.'api/1/'.$postUrl,$postQueryString);
} catch(Exception $e){
print_r($e);
}
}
?>
<html>
<head><title>Scoop hardcore API tester</title></head>
<body>
<div>
<h1>Authentication Stuff</h1>
<form method="POST" action="">
<div><label>Scoop URL</label> <input type="text" name="scoopurl" value="<?php $scoopUrl ?>" size="150"/></div>
<div><label>Local URL</label> <input type="text" name="localurl" value="<?php $localUrl ?>" size="150"/></div>
<div><label>Consumer Key</label> <input type="text" name="consumerkey" value="<?php $consumerKey ?>" size="150"/></div>
<div><label>Consumer Secret</label> <input type="text" name="consumersecret" value="<?php $consumerSecret ?>" size="150"/></div>
<div><input type="submit" name="submit" value="OAuth.it!"/></div>
</form>
<pre>
<?php
if($scoop!=null){
print("Test query: ");
print_r($scoop->get($scoopUrl."api/1/test"));
}
?>
</pre>
<div>
<h1>GET Query Stuff</h1>
<div>
<form method="POST" action="">
<div>
<label>
API method (only last method in url)
</label>
<input type="text" name="url" size="150" value="<?php $url ?>"/>
</div>
<div>
<label>
queryString
</label>
<input type="text" name="queryString" size="150" value="<?php $queryString ?>"/>
</div>
<div>
<input name="submit" type="submit" value="Get.it!"/>
</div>
<pre>
<?php
if($getResult!=null){
print("Result: ");
print_r($getResult);
}
?>
</pre>
</form>
</div>
</div>
<div>
<h1>POST Query Stuff</h1>
<div>
<form method="POST" action="">
<div>
<label>
API method (only last method in url)
</label>
<input type="text" name="postUrl" size="150" value="<?php $postUrl ?>"/>
</div>
<div>
<label>
queryString
</label>
<input type="text" name="postQueryString" size="150" value="<?php $postQueryString ?>"/>
</div>
<div>
<input name="submit" type="submit" value="Post.it!"/>
</div>
<pre>
<?php
if($postResult!=null){
print("Result: ");
print_r($postResult);
}
?>
</pre>
</form>
</div>
</div>
</body>
<?php
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
// vim: foldmethod=marker
/* Generic exception class
*/
class OAuthException extends Exception {/*{{{*/
// pass
}/*}}}*/
class OAuthConsumer {/*{{{*/
public $key;
public $secret;
function __construct($key, $secret, $callback_url=NULL) {/*{{{*/
$this->key = $key;
$this->secret = $secret;
$this->callback_url = $callback_url;
}/*}}}*/
}/*}}}*/
class OAuthToken {/*{{{*/
// access tokens and request tokens
public $key;
public $secret;
/**
* key = the token
* secret = the token secret
*/
function __construct($key, $secret) {/*{{{*/
$this->key = $key;
$this->secret = $secret;
}/*}}}*/
/**
* generates the basic string serialization of a token that a server
* would respond to request_token and access_token calls with
*/
function to_string() {/*{{{*/
return "oauth_token=" . OAuthUtil::urlencodeRFC3986($this->key) .
"&oauth_token_secret=" . OAuthUtil::urlencodeRFC3986($this->secret);
}/*}}}*/
function __toString() {/*{{{*/
return $this->to_string();
}/*}}}*/
}/*}}}*/
class OAuthSignatureMethod {/*{{{*/
public function check_signature(&$request, $consumer, $token, $signature) {
$built = $this->build_signature($request, $consumer, $token);
return $built == $signature;
}
}/*}}}*/
class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {/*{{{*/
function get_name() {/*{{{*/
return "HMAC-SHA1";
}/*}}}*/
public function build_signature($request, $consumer, $token) {/*{{{*/
$base_string = $request->get_signature_base_string();
$request->base_string = $base_string;
$key_parts = array(
$consumer->secret,
($token) ? $token->secret : ""
);
$key_parts = array_map(array('OAuthUtil','urlencodeRFC3986'), $key_parts);
$key = implode('&', $key_parts);
return base64_encode( hash_hmac('sha1', $base_string, $key, true));
}/*}}}*/
}/*}}}*/
class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {/*{{{*/
public function get_name() {/*{{{*/
return "PLAINTEXT";
}/*}}}*/
public function build_signature($request, $consumer, $token) {/*{{{*/
$sig = array(
OAuthUtil::urlencodeRFC3986($consumer->secret)
);
if ($token) {
array_push($sig, OAuthUtil::urlencodeRFC3986($token->secret));
} else {
array_push($sig, '');
}
$raw = implode("&", $sig);
// for debug purposes
$request->base_string = $raw;
// BUGGY LINE
return $raw;
}/*}}}*/
}/*}}}*/
class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {/*{{{*/
public function get_name() {/*{{{*/
return "RSA-SHA1";
}/*}}}*/
protected function fetch_public_cert(&$request) {/*{{{*/
// not implemented yet, ideas are:
// (1) do a lookup in a table of trusted certs keyed off of consumer
// (2) fetch via http using a url provided by the requester
// (3) some sort of specific discovery code based on request
//
// either way should return a string representation of the certificate
throw Exception("fetch_public_cert not implemented");
}/*}}}*/
protected function fetch_private_cert(&$request) {/*{{{*/
// not implemented yet, ideas are:
// (1) do a lookup in a table of trusted certs keyed off of consumer
//
// either way should return a string representation of the certificate
throw Exception("fetch_private_cert not implemented");
}/*}}}*/
public function build_signature(&$request, $consumer, $token) {/*{{{*/
$base_string = $request->get_signature_base_string();
$request->base_string = $base_string;
// Fetch the private key cert based on the request
$cert = $this->fetch_private_cert($request);
// Pull the private key ID from the certificate
$privatekeyid = openssl_get_privatekey($cert);
// Sign using the key
$ok = openssl_sign($base_string, $signature, $privatekeyid);
// Release the key resource
openssl_free_key($privatekeyid);
return base64_encode($signature);
} /*}}}*/
public function check_signature(&$request, $consumer, $token, $signature) {/*{{{*/
$decoded_sig = base64_decode($signature);
$base_string = $request->get_signature_base_string();
// Fetch the public key cert based on the request
$cert = $this->fetch_public_cert($request);
// Pull the public key ID from the certificate
$publickeyid = openssl_get_publickey($cert);
// Check the computed signature against the one passed in the query
$ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
// Release the key resource
openssl_free_key($publickeyid);
return $ok == 1;
} /*}}}*/
}/*}}}*/
class OAuthRequest {/*{{{*/
private $parameters;
private $http_method;
private $http_url;
// for debug purposes
public $base_string;
public static $version = '1.0';
function __construct($http_method, $http_url, $parameters=NULL) {/*{{{*/
@$parameters or $parameters = array();
$this->parameters = $parameters;
$this->http_method = $http_method;
$this->http_url = $http_url;
}/*}}}*/
/**
* attempt to build up a request from what was passed to the server
*/
public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {/*{{{*/
$scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http' : 'https';
@$http_url or $http_url = $scheme . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
@$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
$request_headers = OAuthRequest::get_headers();
// let the library user override things however they'd like, if they know
// which parameters to use then go for it, for example XMLRPC might want to
// do this
if ($parameters) {
$req = new OAuthRequest($http_method, $http_url, $parameters);
}
// next check for the auth header, we need to do some extra stuff
// if that is the case, namely suck in the parameters from GET or POST
// so that we can include them in the signature
else if (@substr($request_headers['Authorization'], 0, 5) == "OAuth") {
$header_parameters = OAuthRequest::split_header($request_headers['Authorization']);
if ($http_method == "GET") {
$req_parameters = $_GET;
}
else if ($http_method == "POST") {
$req_parameters = $_POST;
}
$parameters = array_merge($header_parameters, $req_parameters);
$req = new OAuthRequest($http_method, $http_url, $parameters);
}
else if ($http_method == "GET") {
$req = new OAuthRequest($http_method, $http_url, $_GET);
}
else if ($http_method == "POST") {
$req = new OAuthRequest($http_method, $http_url, $_POST);
}
return $req;
}/*}}}*/
/**
* pretty much a helper function to set up the request
*/
public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {/*{{{*/
@$parameters or $parameters = array();
$defaults = array("oauth_version" => OAuthRequest::$version,
"oauth_nonce" => OAuthRequest::generate_nonce(),
"oauth_timestamp" => OAuthRequest::generate_timestamp(),
"oauth_consumer_key" => $consumer->key);
$parameters = array_merge($defaults, $parameters);
if ($token) {
$parameters['oauth_token'] = $token->key;
}
return new OAuthRequest($http_method, $http_url, $parameters);
}/*}}}*/
public function set_parameter($name, $value) {/*{{{*/
$this->parameters[$name] = $value;
}/*}}}*/
public function get_parameter($name) {/*{{{*/
return $this->parameters[$name];
}/*}}}*/
public function get_parameters() {/*{{{*/
return $this->parameters;
}/*}}}*/
/**
* Returns the normalized parameters of the request
*
* This will be all (except oauth_signature) parameters,
* sorted first by key, and if duplicate keys, then by
* value.
*
* The returned string will be all the key=value pairs
* concated by &.
*
* @return string
*/
public function get_signable_parameters() {/*{{{*/
// Grab all parameters
$params = $this->parameters;
// Remove oauth_signature if present
if (isset($params['oauth_signature'])) {
unset($params['oauth_signature']);
}
// Urlencode both keys and values
$keys = array_map(array('OAuthUtil', 'urlencodeRFC3986'), array_keys($params));
$values = array_map(array('OAuthUtil', 'urlencodeRFC3986'), array_values($params));
$params = array_combine($keys, $values);
// Sort by keys (natsort)
uksort($params, 'strnatcmp');
// Generate key=value pairs
$pairs = array();
foreach ($params as $key=>$value ) {
if (is_array($value)) {
// If the value is an array, it's because there are multiple
// with the same key, sort them, then add all the pairs
natsort($value);
foreach ($value as $v2) {
$pairs[] = $key . '=' . $v2;
}
} else {
$pairs[] = $key . '=' . $value;
}
}
// Return the pairs, concated with &
return implode('&', $pairs);
}/*}}}*/
/**
* Returns the base string of this request
*
* The base string defined as the method, the url
* and the parameters (normalized), each urlencoded
* and the concated with &.
*/
public function get_signature_base_string() {/*{{{*/
$parts = array(
$this->get_normalized_http_method(),
$this->get_normalized_http_url(),
$this->get_signable_parameters()
);
$parts = array_map(array('OAuthUtil', 'urlencodeRFC3986'), $parts);
return implode('&', $parts);
}/*}}}*/
/**
* just uppercases the http method
*/
public function get_normalized_http_method() {/*{{{*/
return strtoupper($this->http_method);
}/*}}}*/
/**
* parses the url and rebuilds it to be
* scheme://host/path
*/
public function get_normalized_http_url() {/*{{{*/
$parts = parse_url($this->http_url);
$port = @$parts['port'];
$scheme = $parts['scheme'];
$host = $parts['host'];
$path = @$parts['path'];
$port or $port = ($scheme == 'https') ? '443' : '80';
if (($scheme == 'https' && $port != '443')
|| ($scheme == 'http' && $port != '80')) {
$host = "$host:$port";
}
return "$scheme://$host$path";
}/*}}}*/
/**
* builds a url usable for a GET request
*/
public function to_url() {/*{{{*/
$out = $this->get_normalized_http_url() . "?";
$out .= $this->to_postdata();
return $out;
}/*}}}*/
/**
* builds the data one would send in a POST request
*/
public function to_postdata() {/*{{{*/
$total = array();
foreach ($this->parameters as $k => $v) {
$total[] = OAuthUtil::urlencodeRFC3986($k) . "=" . OAuthUtil::urlencodeRFC3986($v);
}
$out = implode("&", $total);
return $out;
}/*}}}*/
/**
* builds the Authorization: header
*/
public function to_header($realm="") {/*{{{*/
$out ='"Authorization: OAuth realm="' . $realm . '",';
$total = array();
foreach ($this->parameters as $k => $v) {
if (substr($k, 0, 5) != "oauth") continue;
$out .= ',' . OAuthUtil::urlencodeRFC3986($k) . '="' . OAuthUtil::urlencodeRFC3986($v) . '"';
}
return $out;
}/*}}}*/
public function __toString() {/*{{{*/
return $this->to_url();
}/*}}}*/
public function sign_request($signature_method, $consumer, $token) {/*{{{*/
$this->set_parameter("oauth_signature_method", $signature_method->get_name());
$signature = $this->build_signature($signature_method, $consumer, $token);
$this->set_parameter("oauth_signature", $signature);
}/*}}}*/
public function build_signature($signature_method, $consumer, $token) {/*{{{*/
$signature = $signature_method->build_signature($this, $consumer, $token);
return $signature;
}/*}}}*/
/**
* util function: current timestamp
*/
private static function generate_timestamp() {/*{{{*/
return time();
}/*}}}*/
/**
* util function: current nonce
*/
private static function generate_nonce() {/*{{{*/
$mt = microtime();
$rand = mt_rand();
return md5($mt . $rand); // md5s look nicer than numbers
}/*}}}*/
/**
* util function for turning the Authorization: header into
* parameters, has to do some unescaping
*/
private static function split_header($header) {/*{{{*/
// remove 'OAuth ' at the start of a header
$header = substr($header, 6);
// error cases: commas in parameter values?
$parts = explode(",", $header);
$out = array();
foreach ($parts as $param) {
$param = ltrim($param);
// skip the "realm" param, nobody ever uses it anyway
if (substr($param, 0, 5) != "oauth") continue;
$param_parts = explode("=", $param);
// rawurldecode() used because urldecode() will turn a "+" in the
// value into a space
$out[$param_parts[0]] = rawurldecode(substr($param_parts[1], 1, -1));
}
return $out;
}/*}}}*/
/**
* helper to try to sort out headers for people who aren't running apache
*/
private static function get_headers() {/*{{{*/
if (function_exists('apache_request_headers')) {
// we need this to get the actual Authorization: header
// because apache tends to tell us it doesn't exist
return apache_request_headers();
}
// otherwise we don't have apache and are just going to have to hope
// that $_SERVER actually contains what we need
$out = array();
foreach ($_SERVER as $key => $value) {
if (substr($key, 0, 5) == "HTTP_") {
// this is chaos, basically it is just there to capitalize the first
// letter of every word that is not an initial HTTP and strip HTTP
// code from przemek
$key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5)))));
$out[$key] = $value;
}
}
return $out;
}/*}}}*/
}/*}}}*/
class OAuthServer {/*{{{*/
protected $timestamp_threshold = 300; // in seconds, five minutes
protected $version = 1.0; // hi blaine
protected $signature_methods = array();
protected $data_store;
function __construct($data_store) {/*{{{*/
$this->data_store = $data_store;
}/*}}}*/
public function add_signature_method($signature_method) {/*{{{*/
$this->signature_methods[$signature_method->get_name()] =
$signature_method;
}/*}}}*/
// high level functions
/**
* process a request_token request
* returns the request token on success
*/
public function fetch_request_token(&$request) {/*{{{*/
$this->get_version($request);
$consumer = $this->get_consumer($request);
// no token required for the initial token request
$token = NULL;
$this->check_signature($request, $consumer, $token);
$new_token = $this->data_store->new_request_token($consumer);
return $new_token;
}/*}}}*/
/**
* process an access_token request
* returns the access token on success
*/
public function fetch_access_token(&$request) {/*{{{*/
$this->get_version($request);
$consumer = $this->get_consumer($request);
// requires authorized request token
$token = $this->get_token($request, $consumer, "request");
$this->check_signature($request, $consumer, $token);
$new_token = $this->data_store->new_access_token($token, $consumer);
return $new_token;
}/*}}}*/
/**
* verify an api call, checks all the parameters
*/
public function verify_request(&$request) {/*{{{*/
$this->get_version($request);
$consumer = $this->get_consumer($request);
$token = $this->get_token($request, $consumer, "access");
$this->check_signature($request, $consumer, $token);
return array($consumer, $token);
}/*}}}*/
// Internals from here
/**
* version 1
*/
private function get_version(&$request) {/*{{{*/
$version = $request->get_parameter("oauth_version");
if (!$version) {
$version = 1.0;
}
if ($version && $version != $this->version) {
throw new OAuthException("OAuth version '$version' not supported");
}
return $version;
}/*}}}*/
/**
* figure out the signature with some defaults
*/
private function get_signature_method(&$request) {/*{{{*/
$signature_method =
@$request->get_parameter("oauth_signature_method");
if (!$signature_method) {
$signature_method = "PLAINTEXT";
}
if (!in_array($signature_method,
array_keys($this->signature_methods))) {
throw new OAuthException(
"Signature method '$signature_method' not supported try one of the following: " . implode(", ", array_keys($this->signature_methods))
);
}
return $this->signature_methods[$signature_method];
}/*}}}*/
/**
* try to find the consumer for the provided request's consumer key
*/
private function get_consumer(&$request) {/*{{{*/
$consumer_key = @$request->get_parameter("oauth_consumer_key");
if (!$consumer_key) {
throw new OAuthException("Invalid consumer key");
}
$consumer = $this->data_store->lookup_consumer($consumer_key);
if (!$consumer) {
throw new OAuthException("Invalid consumer");
}
return $consumer;
}/*}}}*/
/**
* try to find the token for the provided request's token key
*/
private function get_token(&$request, $consumer, $token_type="access") {/*{{{*/
$token_field = @$request->get_parameter('oauth_token');
$token = $this->data_store->lookup_token(
$consumer, $token_type, $token_field
);
if (!$token) {
throw new OAuthException("Invalid $token_type token: $token_field");
}
return $token;
}/*}}}*/
/**
* all-in-one function to check the signature on a request
* should guess the signature method appropriately
*/
private function check_signature(&$request, $consumer, $token) {/*{{{*/
// this should probably be in a different method
$timestamp = @$request->get_parameter('oauth_timestamp');
$nonce = @$request->get_parameter('oauth_nonce');
$this->check_timestamp($timestamp);
$this->check_nonce($consumer, $token, $nonce, $timestamp);
$signature_method = $this->get_signature_method($request);
$signature = $request->get_parameter('oauth_signature');
$valid_sig = $signature_method->check_signature(
$request,
$consumer,
$token,
$signature
);
if (!$valid_sig) {
throw new OAuthException("Invalid signature");
}
}/*}}}*/
/**
* check that the timestamp is new enough
*/
private function check_timestamp($timestamp) {/*{{{*/
// verify that timestamp is recentish
$now = time();
if ($now - $timestamp > $this->timestamp_threshold) {
throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
}
}/*}}}*/
/**
* check that the nonce is not repeated
*/
private function check_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
// verify that the nonce is uniqueish
$found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp);
if ($found) {
throw new OAuthException("Nonce already used: $nonce");
}
}/*}}}*/
}/*}}}*/
class OAuthDataStore {/*{{{*/
function lookup_consumer($consumer_key) {/*{{{*/
// implement me
}/*}}}*/
function lookup_token($consumer, $token_type, $token) {/*{{{*/
// implement me
}/*}}}*/
function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
// implement me
}/*}}}*/
function fetch_request_token($consumer) {/*{{{*/
// return a new token attached to this consumer
}/*}}}*/
function fetch_access_token($token, $consumer) {/*{{{*/
// return a new access token attached to this consumer
// for the user associated with this token if the request token
// is authorized
// should also invalidate the request token
}/*}}}*/
}/*}}}*/
/* A very naive dbm-based oauth storage
*/
class SimpleOAuthDataStore extends OAuthDataStore {/*{{{*/
private $dbh;
function __construct($path = "oauth.gdbm") {/*{{{*/
$this->dbh = dba_popen($path, 'c', 'gdbm');
}/*}}}*/
function __destruct() {/*{{{*/
dba_close($this->dbh);
}/*}}}*/
function lookup_consumer($consumer_key) {/*{{{*/
$rv = dba_fetch("consumer_$consumer_key", $this->dbh);
if ($rv === FALSE) {
return NULL;
}
$obj = unserialize($rv);
if (!($obj instanceof OAuthConsumer)) {
return NULL;
}
return $obj;
}/*}}}*/
function lookup_token($consumer, $token_type, $token) {/*{{{*/
$rv = dba_fetch("${token_type}_${token}", $this->dbh);
if ($rv === FALSE) {
return NULL;
}
$obj = unserialize($rv);
if (!($obj instanceof OAuthToken)) {
return NULL;
}
return $obj;
}/*}}}*/
function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
if (dba_exists("nonce_$nonce", $this->dbh)) {
return TRUE;
} else {
dba_insert("nonce_$nonce", "1", $this->dbh);
return FALSE;
}
}/*}}}*/
function new_token($consumer, $type="request") {/*{{{*/
$key = md5(time());
$secret = time() + time();
$token = new OAuthToken($key, md5(md5($secret)));
if (!dba_insert("${type}_$key", serialize($token), $this->dbh)) {
throw new OAuthException("doooom!");
}
return $token;
}/*}}}*/
function new_request_token($consumer) {/*{{{*/
return $this->new_token($consumer, "request");
}/*}}}*/
function new_access_token($token, $consumer) {/*{{{*/
$token = $this->new_token($consumer, 'access');
dba_delete("request_" . $token->key, $this->dbh);
return $token;
}/*}}}*/
}/*}}}*/
class OAuthUtil {/*{{{*/
public static function urlencodeRFC3986($string) {/*{{{*/
return str_replace('+', ' ',
str_replace('%7E', '~', rawurlencode($string)));
}/*}}}*/
// This decode function isn't taking into consideration the above
// modifications to the encoding process. However, this method doesn't
// seem to be used anywhere so leaving it as is.
public static function urldecodeRFC3986($string) {/*{{{*/
return rawurldecode($string);
}/*}}}*/
}/*}}}*/
?>
<?php
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
// Dependencies json - hash - pecl_http or cUrl (cUrl is used by default, and should
// be present in all PHP distributions)
//
include_once('OAuth.php');
// TokenStore store authentication tokens for the current user
// you can implements your own TokenStore or
// use the provided SessionTokenStore if you are not afraid of using sessions
interface TokenStore {
// Store methods
public function storeRequestToken($value);
public function storeAccessToken($value);
public function storeSecret($value);
public function storeVerifier($value);
// get methods
public function getRequestToken();
public function getAccessToken();
public function getSecret();
public function getVerifier();
// flush methods.
public function flushRequestToken();
public function flushAccessToken();
public function flushSecret();
public function flushVerifier();
}
// Store authentication tokens in the session
class SessionTokenStore implements TokenStore{
public function __construct(){
session_start();
}
// store
public function storeRequestToken($value){
$_SESSION['scoop.requestToken']=$value;
}
public function storeAccessToken($value){
$_SESSION['scoop.accessToken']=$value;
}
public function storeVerifier($value){
$_SESSION['scoop.verifier']=$value;
}
public function storeSecret($value){
$_SESSION['scoop.secret']=$value;
}
// get
public function getRequestToken(){
return $_SESSION['scoop.requestToken'];
}
public function getAccessToken(){
return $_SESSION['scoop.accessToken'];
}
public function getVerifier(){
return $_SESSION['scoop.verifier'];
}
public function getSecret(){
return $_SESSION['scoop.secret'];
}
// flush
public function flushRequestToken(){
session_unregister('scoop.requestToken');
}
public function flushAccessToken(){
session_unregister('scoop.accessToken');
}
public function flushVerifier(){
session_unregister('scoop.verifier');
}
public function flushSecret(){
session_unregister('scoop.secret');
}
}
// Used to provide custom http code if you hate default curl code :P
interface ScoopHttpBackend{
public function executeHttpGet($url);
public function executeHttpPost($url,$putString);
}
// Default curl implementation this is some crap.
class ScoopCurlHttpBackend implements ScoopHttpBackend {
// The folowing code uses curl as http backend.
// Note that this is really crappy, pecl_http really has a better interface.
public function executeHttpGet($url){
//die($url);
$curlHandler = curl_init();
curl_setopt($curlHandler, CURLOPT_URL, $url);
curl_setopt($curlHandler,CURLOPT_RETURNTRANSFER,1);
try {
$body = curl_exec($curlHandler);
$status = curl_getinfo($curlHandler,CURLINFO_HTTP_CODE);
if($status!=200){
throw new ScoopHttpNot200Exception($url,$body,$status);
}
curl_close($curlHandler);
return $body;
}catch(Exception $e){
curl_close($curlHandler);
throw $e;
}
}
public function executeHttpPost($url,$postData){
$curlHandler = curl_init();
curl_setopt($curlHandler, CURLOPT_URL, $url);
curl_setopt($curlHandler,CURLOPT_RETURNTRANSFER,true);
// THE CRAPIEST THING I'VE EVER SEEN :
$putData = tmpfile();
fwrite($putData, $putString);
fseek($putData, 0);
curl_setopt($curlHandler, CURLOPT_POSTFIELDS, $postData);
try {
$body = curl_exec($curlHandler);
$status = curl_getinfo($curlHandler,CURLINFO_HTTP_CODE);
if($status!=200){
throw new ScoopHttpNot200Exception($url,$body,$status);
}
curl_close($curlHandler);
return $body;
}catch(Exception $e){
curl_close($curlHandler);
throw $e;
}
}
}
// Execute request to Scoop. Requests are oauth authenticated
class ScoopExecutor {
private $consumerToken;
private $accessToken;
private $signatureMethod;
private $httpBackend;
function __construct($consumerToken,$accessToken,$httpBackend){
$this->consumerToken = $consumerToken;
$this->accessToken = $accessToken;
$this->signatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
$this->httpBackend = $httpBackend;
}
// url must not contain any parameters
function execute($url){
$parsed = parse_url($url);
$params = array();
parse_str($parsed['query'], $params);
$req = OAuthRequest::from_consumer_and_token($this->consumerToken, $this->accessToken, "GET", $url, $params);
$req->sign_request($this->signatureMethod,$this->consumerToken, $this->accessToken);
try {
//die($req->to_url());
$responseBody = $this->httpBackend->executeHttpGet($req->to_url());
return json_decode($responseBody);
} catch(ScoopHttpNot200Exception $e) {
throw new ScoopAuthenticationException("Unable to execute opensocial query, server response : ".$e->toString());
}
}
function executeDelete($url, $params=array()){
$req = OAuthRequest::from_consumer_and_token($this->consumerToken, $this->accessToken, "DELETE", $url, $params);
$req->sign_request($this->signatureMethod,$this->consumerToken, $this->accessToken);
try {
$responseBody = $this->httpBackend->executeHttpDelete($req->to_url());
return json_decode($responseBody);
} catch(ScoopHttpNot200Exception $e) {
throw new ScoopAuthenticationException("Unable to execute opensocial query, server response : ".$e->toString());
}
}
function executePost($url,$postData){
if($postData==null || $postData==""){
throw new Exception("Null data");
}
$req = OAuthRequest::from_consumer_and_token($this->consumerToken, $this->accessToken, "POST", $url, array());
$req->sign_request($this->signatureMethod,$this->consumerToken, $this->accessToken);
try {
$responseBody = $this->httpBackend->executeHttpPost($req->to_url(), $postData);
return json_decode($responseBody);
} catch(ScoopHttpNot200Exception $e) {
throw new ScoopAuthenticationException("Unable to execute opensocial query, server response : ".$e->toString());
}
}
function executePut($url,$putData){
if($putData==null || $putData==""){
throw new Exception("Null data");
}
$req = OAuthRequest::from_consumer_and_token($this->consumerToken, $this->accessToken, "PUT", $url, array());
$req->sign_request($this->signatureMethod,$this->consumerToken, $this->accessToken);
try {
$responseBody = $this->httpBackend->executeHttpPut($req->to_url(), $putData);
return json_decode($responseBody);
} catch(ScoopHttpNot200Exception $e) {
throw new ScoopAuthenticationException("Unable to execute opensocial query, server response : ".$e->toString());
}
}
}
// You may want to catch this to present a decent = error message for you're
// user ;)
// Every call to every method (including the constructor) of the Scoop object
// may throw this exception.
class ScoopAuthenticationException extends Exception {
public function __construct($message){
parent::__construct($message);
}
}
class ScoopHttpNot200Exception extends Exception {
public $body;
public $status;
public $url;
public function __construct($url,$body, $status){
$this->url=$url;
$this->body = $body;
$this->status = $status;
}
public function toString(){
return "Url: $this->url\nStatus: $this->status\nBody: $this->body";
}
}
class ScoopConstants {
}
class Scoop {
private $gjServer="http://www.scoop.it/";
private $gjRequestTokenUrl;
private $gjAccessTokenUrl;
private $gjAuthorizeUrl ;
private $signatureMethod ;
private $myUrl;
private $tokenStore;
private $consumer;
private $executor;
private $httpBackend;
// $tokenStore : the TokenStore to be used to store authentication tokens
// $myUrl : the url that calls this script (used to make external
// redirection of the OAuth Core 1.0 protocol)
// $consumerKey the oauth consumer key (provided by Scoop)
// $consumerSecret the oauth consumer secret (provided by Scoop)
// $httpBackend : optional object used to do http request to scoop.com
// by default it will be an instance of ScoopCurlHttpBackend, and if you do
// not have cUrl, you can use pecl_http, just provide an instance of
// ScoopPeclHttpBackend...
//
// This method construct the Scoop object and authenticate the current user
// This can do external redirection so, be sure to fill myUrl apprioriately
public function __construct($tokenStore, $myUrl, $consumerKey, $consumerSecret, $scoopServers = null, $httpBackend = null){
if($scoopServers != null){
$this->gjServer=$scoopServers;
}
$this->gjRequestTokenUrl=$this->gjServer."oauth/request";
$this->gjAccessTokenUrl=$this->gjServer."oauth/access";
$this->gjAuthorizeUrl=$this->gjServer."oauth/authorize";
$this->signatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
$this->tokenStore = $tokenStore;
$this->myUrl = $myUrl;
$this->consumer=new OAuthConsumer($consumerKey,$consumerSecret,NULL);
if($httpBackend == null){
$this->httpBackend = new ScoopCurlHttpBackend();
} else {
$this->httpBackend = $httpBackend;
}
$this->authenticate();
}
public function get($url){
return $this->executor->execute($url);
}
public function post($url,$postData){
return $this->executor->executePost($url,$postData);
}
public function reauthenticate(){
$this->tokenStore->flushRequestToken();
$this->tokenStore->flushAccessToken();
$this->tokenStore->flushSecret();
$this->authenticate();
}
// Main authentication job is done here !
// this method is awful
private function authenticate(){
$accessToken=$this->tokenStore->getAccessToken();
// Note that in this code the secret can be the one of the request token
// or the access token
$secret=$this->tokenStore->getSecret();
if($accessToken == null){
// we store the previously requested request token in the session...
$requestToken=$this->tokenStore->getRequestToken();
// The request token is only present in one case : the callback has been
// called by scoop. So try to authenticate it and grab an access token !
if($requestToken!=null){
// try to grab the access token
$token = new OAuthConsumer($requestToken,$secret);
$parsed = parse_url($this->gjAccessTokenUrl);
$params = array();
// add verifier
parse_str($parsed['query'], $params);
$params['oauth_verifier'] = $_GET['oauth_verifier'];
$acc_req = OAuthRequest::from_consumer_and_token($this->consumer, $token, "GET", $this->gjAccessTokenUrl, $params);
$acc_req->sign_request($this->signatureMethod, $this->consumer, $token);
// die($acc_req->to_url());
// Execute the HTTP request & parse result as a tokens TODO FACTORIZE
$resultParams = array();
try {
$responseBody = $this->httpBackend->executeHttpGet($acc_req->to_url());
parse_str($responseBody, $resultParams);
} catch(ScoopHttpNot200Exception $e) {
// Do nothing : this will flush the request token and request a new request token
}
$accessToken=$resultParams['oauth_token'];
$secret= $resultParams['oauth_token_secret'];
// store token and secret in the session for future use
$this->tokenStore->storeSecret($secret);
$this->tokenStore->storeAccessToken($accessToken);
$this->tokenStore->flushRequestToken();
}
if($accessToken==null){
// no access nor request token, requets a request token and
// authorize this application to access to gj data.
$parsed = parse_url($this->gjRequestTokenUrl);
$params = array();
parse_str($parsed['query'], $params);
$acc_req = OAuthRequest::from_consumer_and_token($this->consumer, NULL, "GET", $this->gjRequestTokenUrl, $params);
$acc_req->sign_request($this->signatureMethod, $this->consumer, NULL);
// Execute the HTTP request & parse result as a tokens TODO FACTORIZE
$resultParams = array();
try {
$responseBody = $this->httpBackend->executeHttpGet($acc_req->to_url());
parse_str($responseBody, $resultParams);
} catch(ScoopHttpNot200Exception $e) {
throw new ScoopAuthenticationException("Unable to get a request token from Scoop, server response : ".$e->toString());
}
// parse response body
$requestToken=$resultParams['oauth_token'];
// store token and secret in the session for future use
$this->tokenStore->storeSecret($resultParams['oauth_token_secret']);
$this->tokenStore->storeRequestToken($requestToken);
// redirect to the authroization url of scoop :
// the user will then be asked to log in (in scoop) if needed
// and it will have to accept that our application will
// access to it's personal data.
Header("Location: ".$this->gjAuthorizeUrl."?oauth_token=$requestToken&oauth_callback=".urlencode($this->myUrl));
exit;
}
}
// We are authenticated, construct the executor
$token = new OauthConsumer($accessToken,$secret);
$this->executor = new ScoopExecutor($this->consumer,$token,$this->httpBackend);
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment