Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
593 lines (536 sloc) 25.2 KB
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>JavaScript OAuth 1.0 Call Testing Tool</title>
<script src="./jquery.min.js" type="text/javascript">
</script>
<script type="text/javascript">
//<![CDATA[
/* OAuthSimple
* A simpler version of OAuth
* NOTE: this page uses a modified version of the OAuthSimple library due to constraints with how this page is served. Please see
* http://github.com/jrconlin/oauthsimple for the proper library.
*
* author: jr conlin
* mail: src thatWeirdACirclyThing anticipatr dot com
* copyright: unitedHeroes.net
* version: 0.2 (Not Ready For Prime Time)
* url: http://unitedHeroes.net/OAuthSimple
*
* Copyright (c) 2008, unitedHeroes.net
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the unitedHeroes.net nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY UNITEDHEROES.NET ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UNITEDHEROES.NET BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var OAuthSimple;
var testURL;
if (OAuthSimple == null)
{
/* Simple OAuth
*
* This class only builds the OAuth elements, it does not do the actual
* transmission or reception of the tokens. It does not validate elements
* of the token. It is for client use only.
*
* api_key is the API key, also known as the OAuth consumer key
* shared_secret is the shared secret (duh).
*
* Both the api_key and shared_secret are generally provided by the site
* offering OAuth services. You need to specify them at object creation
* because nobody <explative>ing uses OAuth without that minimal set of
* signatures.
*
* If you want to use the higher order security that comes from the
* OAuth token (sorry, I don't provide the functions to fetch that because
* sites aren't horribly consistent about how they offer that), you need to
* pass those in either with .setTokensAndSecrets() or as an argument to the
* .sign() or .getHeaderString() functions.
*
* Example:
<code>
var oauthObject = OAuthSimple().sign({path:'http://example.com/rest/',
parameters: 'foo=bar&gorp=banana',
signatures:{
api_key:'12345abcd',
shared_secret:'xyz-5309'
}});
document.getElementById('someLink').href=oauthObject.signed_url;
<\/code>
*
* that will sign as a "GET" using "SHA1-MAC" the url. If you need more than
* that, read on, McDuff.
*/
/** OAuthSimple creator
*
* Create an instance of OAuthSimple
*
* @param api_key {string} The API Key (sometimes referred to as the consumer key) This value is usually supplied by the site you wish to use.
* @param shared_secret (string) The shared secret. This value is also usually provided by the site you wish to use.
*/
OAuthSimple = function (consumer_key,shared_secret)
{
this._secrets={};
// General configuration options.
if (consumer_key != null)
this._secrets['consumer_key'] = consumer_key;
if (shared_secret != null)
this._secrets['shared_secret'] = shared_secret;
this._default_signature_method= "HMAC-SHA1";
this._action = "GET";
this._nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/** set the parameters either from a hash or a string
*
* @param {string,object} List of parameters for the call, this can either be a URI string (e.g. "foo=bar&gorp=banana" or an object/hash)
*/
this.setParameters = function (parameters) {
if (parameters == null)
parameters = {};
if (typeof(parameters) == 'string')
parameters=this._parseParameterString(parameters);
this._parameters = parameters;
if (this._parameters['oauth_nonce'] == null)
this._getNonce();
if (this._parameters['oauth_timestamp'] == null)
this._getTimestamp();
if (this._parameters['oauth_method'] == null)
this.setSignatureMethod();
if (this._parameters['oauth_consumer_key'] == null)
this._getApiKey();
if(this._parameters['oauth_token'] == null)
this._getAccessToken();
return this;
};
/** convienence method for setParameters
*
* @param parameters {string,object} See .setParameters
*/
this.setQueryString = function (parameters) {
return this.setParameters(parameters);
};
/** Set the target URL (does not include the parameters)
*
* @param path {string} the fully qualified URI (excluding query arguments) (e.g "http://example.org/foo")
*/
this.setURL = function (path) {
if (path == '')
throw ('No path specified for OAuthSimple.setURL');
if (path.indexOf(' ') != -1) {
$("#path").select();
throw ('Space detected in request path/URL');
};
this._path = path;
return this;
};
/** convienence method for setURL
*
* @param path {string} see .setURL
*/
this.setPath = function(path){
return this.setURL(path);
};
/** set the "action" for the url, (e.g. GET,POST, DELETE, etc.)
*
* @param action {string} HTTP Action word.
*/
this.setAction = function(action) {
if (action == null)
action="GET";
action = action.toUpperCase();
if (action.match('[^A-Z]'))
throw ('Invalid action specified for OAuthSimple.setAction');
this._action = action;
return this;
};
/** set the signatures (as well as validate the ones you have)
*
* @param signatures {object} object/hash of the token/signature pairs {api_key:, shared_secret:, oauth_token: oauth_secret:}
*/
this.setTokensAndSecrets = function(signatures) {
if (signatures)
for (var i in signatures)
this._secrets[i] = signatures[i];
// Aliases
if (this._secrets['api_key'])
this._secrets.consumer_key = this._secrets.api_key;
if (this._secrets['access_token'])
this._secrets.oauth_token = this._secrets.access_token;
if (this._secrets['access_secret'])
this._secrets.oauth_secret = this._secrets.access_secret;
// Gauntlet
if (this._secrets.consumer_key == null)
throw('Missing required consumer_key in OAuthSimple.setTokensAndSecrets');
if (this._secrets.shared_secret == null)
throw('Missing required shared_secret in OAuthSimple.setTokensAndSecrets');
if ((this._secrets.oauth_token!=null) && (this._secrets.oauth_secret == null))
throw('Missing oauth_secret for supplied oauth_token in OAuthSimple.setTokensAndSecrets');
return this;
};
/** set the signature method (currently only Plaintext or SHA-MAC1)
*
* @param method {string} Method of signing the transaction (only PLAINTEXT and SHA-MAC1 allowed for now)
*/
this.setSignatureMethod = function(method) {
if (method == null)
method = this._default_signature_method;
//TODO: accept things other than PlainText or SHA-MAC1
if (method.toUpperCase().match(/(PLAINTEXT|HMAC-SHA1)/) == null)
throw ('Unknown signing method specified for OAuthSimple.setSignatureMethod');
this._parameters['oauth_signature_method']= method.toUpperCase();
return this;
};
/** sign the request
*
* note: all arguments are optional, provided you've set them using the
* other helper functions.
*
* @param args {object} hash of arguments for the call
* {action:, path:, parameters:, method:, signatures:}
* all arguments are optional.
*/
this.sign = function (args) {
if (args == null)
args = {};
// Set any given parameters
if(args['action'] != null)
this.setAction(args['action']);
if (args['path'] != null)
this.setPath(args['path']);
if (args['method'] != null)
this.setSignatureMethod(args['method']);
this.setTokensAndSecrets(args['signatures']);
if (args['parameters'] != null)
this.setParameters(args['parameters']);
// check the parameters
var normParams = this._normalizedParameters();
var sig = this._generateSignature(normParams);
this._parameters['oauth_signature']=sig.signature;
return {
parameters: this._parameters,
sig_string: sig.sig_string,
signature: this._oauthEscape(this._parameters['oauth_signature']),
signed_url: this._path + '?' + this._normalizedParameters(),
header: this.getHeaderString()
};
};
/** Return a formatted "header" string
*
* NOTE: This doesn't set the "Authorization: " prefix, which is required.
* I don't set it because various set header functions prefer different
* ways to do that.
*
* @param args {object} see .sign
*/
this.getHeaderString = function(args) {
if (this._parameters['oauth_signature'] == null)
this.sign(args);
var result = 'OAuth ';
for (var pName in this._parameters)
{
if (pName.match(/^oauth/) == null)
continue;
if ((this._parameters[pName]) instanceof Array)
{
var pLength = this._parameters[pName].length;
for (var j=0;j<pLength;j++)
{
result += pName +'="'+this._oauthEscape(this._parameters[pName][j])+'" ';
}
}
else
{
result += pName + '="'+this._oauthEscape(this._parameters[pName])+'" ';
}
}
return result;
};
// Start Private Methods.
/** convert the parameter string into a hash of objects.
*
*/
this._parseParameterString = function(paramString){
var elements = paramString.split('&');
var result={};
for(var element=elements.shift();element;element=elements.shift())
{
var keyToken=element.split('=');
var value;
if (keyToken[1])
value = decodeURIComponent(keyToken[1]);
if(result[keyToken[0]]){
if (!(result[keyToken[0]] instanceof Array))
{
result[keyToken[0]] = Array(result[keyToken[0]],value);
}
else
{
result[keyToken[0]].push(value);
}
}
else
{
result[keyToken[0]]=value;
}
}
return result;
};
this._oauthEscape = function(string) {
if (string == null)
return "";
if (string instanceof Array)
{
throw('Array passed to _oauthEscape');
}
return encodeURIComponent(string).replace(/\!/g, "%21").
replace(/\*/g, "%2A").
replace(/'/g, "%27").
replace(/\(/g, "%28").
replace(/\)/g, "%29");
};
this._getNonce = function (length) {
if (length == null)
length=5;
var result = "";
var cLength = this._nonce_chars.length;
for (var i = 0; i < length;i++) {
var rnum = Math.floor(Math.random() *cLength);
result += this._nonce_chars.substring(rnum,rnum+1);
}
this._parameters['oauth_nonce']=result;
return result;
};
this._getApiKey = function() {
if (this._secrets.consumer_key == null)
throw('No consumer_key set for OAuthSimple.');
this._parameters['oauth_consumer_key']=this._secrets.consumer_key;
return this._parameters.oauth_consumer_key;
};
this._getAccessToken = function() {
if (this._secrets['oauth_secret'] == null)
return '';
if (this._secrets['oauth_token'] == null)
throw('No oauth_token (access_token) set for OAuthSimple.');
this._parameters['oauth_token'] = this._secrets.oauth_token;
return this._parameters.oauth_token;
};
this._getTimestamp = function() {
var d = new Date();
var ts = Math.floor(d.getTime()/1000);
this._parameters['oauth_timestamp'] = ts;
return ts;
};
this._normalizedParameters = function() {
var elements = new Array();
var paramNames = [];
var ra =0;
for (var paramName in this._parameters)
{
if (ra++ > 1000)
throw('runaway 1');
paramNames.unshift(paramName);
}
paramNames = paramNames.sort();
pLen = paramNames.length;
for (var i=0;i<pLen; i++)
{
paramName=paramNames[i];
//skip secrets.
if (paramName.match(/\w+_secret/))
continue;
if (this._parameters[paramName] instanceof Array)
{
var sorted = this._parameters[paramName].sort();
var spLen = sorted.length;
for (var j = 0;j<spLen;j++){
if (ra++ > 1000)
throw('runaway 1');
elements.push(this._oauthEscape(paramName) + '=' +
this._oauthEscape(sorted[j]));
}
continue;
}
elements.push(this._oauthEscape(paramName) + '=' +
this._oauthEscape(this._parameters[paramName]));
}
return elements.join('&');
};
this.b64_hmac_sha1 = function(k,d,_p,_z){
// heavily optimized and compressed version of http://pajhome.org.uk/crypt/md5/sha1.js
// _p = b64pad, _z = character size; not used here but I left them available just in case
if(!_p){_p='=';}if(!_z){_z=8;}function _f(t,b,c,d){if(t<20){return(b&c)|((~b)&d);}if(t<40){return b^c^d;}if(t<60){return(b&c)|(b&d)|(c&d);}return b^c^d;}function _k(t){return(t<20)?1518500249:(t<40)?1859775393:(t<60)?-1894007588:-899497514;}function _s(x,y){var l=(x&0xFFFF)+(y&0xFFFF),m=(x>>16)+(y>>16)+(l>>16);return(m<<16)|(l&0xFFFF);}function _r(n,c){return(n<<c)|(n>>>(32-c));}function _c(x,l){x[l>>5]|=0x80<<(24-l%32);x[((l+64>>9)<<4)+15]=l;var w=[80],a=1732584193,b=-271733879,c=-1732584194,d=271733878,e=-1009589776;for(var i=0;i<x.length;i+=16){var o=a,p=b,q=c,r=d,s=e;for(var j=0;j<80;j++){if(j<16){w[j]=x[i+j];}else{w[j]=_r(w[j-3]^w[j-8]^w[j-14]^w[j-16],1);}var t=_s(_s(_r(a,5),_f(j,b,c,d)),_s(_s(e,w[j]),_k(j)));e=d;d=c;c=_r(b,30);b=a;a=t;}a=_s(a,o);b=_s(b,p);c=_s(c,q);d=_s(d,r);e=_s(e,s);}return[a,b,c,d,e];}function _b(s){var b=[],m=(1<<_z)-1;for(var i=0;i<s.length*_z;i+=_z){b[i>>5]|=(s.charCodeAt(i/8)&m)<<(32-_z-i%32);}return b;}function _h(k,d){var b=_b(k);if(b.length>16){b=_c(b,k.length*_z);}var p=[16],o=[16];for(var i=0;i<16;i++){p[i]=b[i]^0x36363636;o[i]=b[i]^0x5C5C5C5C;}var h=_c(p.concat(_b(d)),512+d.length*_z);return _c(o.concat(h),512+160);}function _n(b){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s='';for(var i=0;i<b.length*4;i+=3){var r=(((b[i>>2]>>8*(3-i%4))&0xFF)<<16)|(((b[i+1>>2]>>8*(3-(i+1)%4))&0xFF)<<8)|((b[i+2>>2]>>8*(3-(i+2)%4))&0xFF);for(var j=0;j<4;j++){if(i*8+j*6>b.length*32){s+=_p;}else{s+=t.charAt((r>>6*(3-j))&0x3F);}}}return s;}function _x(k,d){return _n(_h(k,d));}return _x(k,d);
};
this._generateSignature = function() {
var secretKey = this._oauthEscape(this._secrets.shared_secret)+'&'+
this._oauthEscape(this._secrets.oauth_secret);
if (this._parameters['oauth_signature_method'] == 'PLAINTEXT')
{
return {sig_string:null,signature:secretKey};
}
if (this._parameters['oauth_signature_method'] == 'HMAC-SHA1')
{
var sigString = this._oauthEscape(this._action)+'&'+this._oauthEscape(this._path)+'&'+this._oauthEscape(this._normalizedParameters());
return {'sig_string':sigString,'signature':this.b64_hmac_sha1(secretKey,sigString)};
}
return null;
};
return this;
}
}
function showAdvanced() {
document.getElementById('advanced').style.display="block";
document.getElementById('action_p').style.display="block";
}
function populate() {
var d = new Date();
document.getElementById('timestamp').value = Math.floor(d.getTime()/1000);
if (!document.getElementById('nonce').value)
document.getElementById('nonce').value = '1234';
jQuery('#advswitch').click(showAdvanced);
return;
}
function generate() {
var args=document.getElementById('args').value;
if (args)
args += '&';
/* Note: normally, you don't need to glom the parameters like this, but since we're allowing the user to overwrite them... */
var oauth = OAuthSimple();
oauth.setAction(document.getElementById('action').value);
try {
var o = oauth.sign({path:document.getElementById('path').value,
parameters: args+'oauth_nonce='+document.getElementById('nonce').value+'&oauth_timestamp='+document.getElementById('timestamp').value+'&oauth_version='+document.getElementById('version').value,
signatures:{
api_key:document.getElementById('key').value,
shared_secret:document.getElementById('secret').value,
access_token:document.getElementById('accessToken').value,
access_secret:document.getElementById('accessSecret').value
}
});
$("#testLink").show();
$("#result").show();
document.getElementById('result').innerHTML=o.signed_url;
testURL = o.signed_url;
document.getElementById('advanced').innerHTML = '<i>Signature String:<\/i> '+o.sig_string+'<br />';
}catch (e)
{ alert (e); }
return false;
}
function testLink() {
$("#divTestOutput").show();
$("#testOutput").attr('src',testURL);
$("#resetLink").show();
}
function resetLink() {
$("#testOutput").attr('src','about:blank');
$("#divTestOutput").hide();
$("#resetLink").hide();
}
$(document).ready(function() {populate();jQuery('#refresh').click(function(){populate();});$('#testLink').click(function(){testLink();});$("#resetLink").click(function(){resetLink();});});
//]]>
</script>
<style type="text/css" media="screen">
/*<![CDATA[*/
<!--
body {font-family:Helvetica,Arial;font-size:9pt;}
#oauth_test p {margin:.5em 0 0 0;padding:0;width:900px;}
#oauth_test input {border:1px solid #DDD;background-color:#EEE;width:400px;}
#oauth_test label {width:100px;float:left;text-align:right; margin-right:.5em;}
#oauth_test button {margin-left:100px;}
#oauth_test hr {width:512px;margin-left: 0px;}
#oauth_test #result {width:500px;background-color:#EEF;margin:1em;padding:.5em;font-family:"courier new";font-size:10px;}
#oauth_test .optional {color:#888;}
#oauth_test .sample {margin:0 0 0 110px;padding:0;color:#AAA;font-style:italic;}
#oauth_test #advswitch {text-align:right; color:#888;}
#oauth_test #refresh {text-decoration:underline;}
#oauth_test #advanced {font-family:"courier new";background-color:#EEF; overflow:scroll;white-space:nowrap;}
#oauth_test #action_p {display:block;}
-->
/*]]>*/
</style>
</head>
<body>
<h2 class="first">JavaScript OAuth 1.0 Testing Tool</h2>
<div id="container" style="border: px coral solid;width:100%;">
<div id="oauth_test" style="float:left;width:550px;">
<form id="form" onsubmit="generate();return false;" name="form">
<p><label for="key" title=
"Also known as Consumer Key - the value provided when you registered your key or application.">
API Key:</label><input id="key" name="key" title=
"Also known as Consumer Key - the value provided when you registered your key or application." /></p>
<p><label for="secret">Shared Secret:</label><input id="secret" name="secret"
title="Shared secret that is associated with your consumer key." /></p>
<p><label class="optional" for="secret" title=
"Also known as User Token - a value provided after a user grants permission to your app.">
Access Token:</label><input id="accessToken" name="token" title=
"Also known as User Token -- a value provided after a user grants permission to your app." />
<span style="color:#888;font-size:12px;">(optional)</span></p>
<p><label class="optional" for="secret">Access Secret:</label><input id=
"accessSecret" name="tokensecret" /> <span style=
"color:#888;font-size:12px;">(optional)</span></p>
<hr />
<p id="action_p"><label for="action">Action:</label><select id="action" name=
"action">
<option value="GET">
GET
</option>
<option value="POST">
POST
</option>
<option value="PUT">
PUT
</option>
<option value="DELETE">
DELETE
</option>
</select></p>
<p><label for="path">Path:</label><input type="url" id="path" name="path" /></p>
<div class="sample">
Netflix example: http://api.netflix.com/catalog/titles
</div>
<p><label for="args">Args:</label><input id="args" name="args" /></p>
<div class="sample">
Netflix example: term=heavenly%20creatures&amp;output=json
</div>
<p><label for="nonce">Nonce:</label><input id="nonce" name="nonce" /></p>
<hr />
<p><label for="timestamp">Timestamp:</label><input id="timestamp" name=
"timestamp" style="width:100px;" /><span style=
"margin-left: 20px;font-size:12px;color:#888;" id="refresh">Refresh</span></p>
<p><label for="version">Version:</label><input id="version" name="version" value=
"1.0" style="width:100px;" /></p>
</form>
<p><button id="doit" onclick="generate();return false;">Generate Signed
Call</button></p>
<p id="result" style="display:none;">&nbsp;</p>
<p><button id="testLink" style="display:none;">Execute Live Call</button>
<button id="resetLink" style="display:none;">Clear API Response</button></p><br />
<p id="advanced" style="display:none;">&nbsp;</p>
</div><!-- oauth -->
<div id="right" style="position:absolute;left:590px;top:10px;width:500px;">
<div id="divTestOutput" style="display:none;">
<iframe src="about:blank" id="testOutput" style="width:100%;height:450px;" name=
"testOutput"></iframe>
</div>
</div>
</div><!-- container -->
</body>
</html>