Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 /* OAuthSimple | |
| 2 * A simpler version of OAuth | |
| 3 * | |
| 4 * author: jr conlin | |
| 5 * mail: src@anticipatr.com | |
| 6 * copyright: unitedHeroes.net | |
| 7 * version: 1.0 | |
| 8 * url: http://unitedHeroes.net/OAuthSimple | |
| 9 * | |
| 10 * Copyright (c) 2009, unitedHeroes.net | |
| 11 * All rights reserved. | |
| 12 * | |
| 13 * Redistribution and use in source and binary forms, with or without | |
| 14 * modification, are permitted provided that the following conditions are met: | |
| 15 * * Redistributions of source code must retain the above copyright | |
| 16 * notice, this list of conditions and the following disclaimer. | |
| 17 * * Redistributions in binary form must reproduce the above copyright | |
| 18 * notice, this list of conditions and the following disclaimer in the | |
| 19 * documentation and/or other materials provided with the distribution. | |
| 20 * * Neither the name of the unitedHeroes.net nor the | |
| 21 * names of its contributors may be used to endorse or promote products | |
| 22 * derived from this software without specific prior written permission. | |
| 23 * | |
| 24 * THIS SOFTWARE IS PROVIDED BY UNITEDHEROES.NET ''AS IS'' AND ANY | |
| 25 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| 26 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 27 * DISCLAIMED. IN NO EVENT SHALL UNITEDHEROES.NET BE LIABLE FOR ANY | |
| 28 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| 29 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| 30 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| 31 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THI S | |
| 33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 34 */ | |
| 35 var OAuthSimple; | |
| 36 | |
| 37 if (OAuthSimple === undefined) | |
| 38 { | |
| 39 /* Simple OAuth | |
| 40 * | |
| 41 * This class only builds the OAuth elements, it does not do the actual | |
| 42 * transmission or reception of the tokens. It does not validate elements | |
| 43 * of the token. It is for client use only. | |
| 44 * | |
| 45 * api_key is the API key, also known as the OAuth consumer key | |
| 46 * shared_secret is the shared secret (duh). | |
| 47 * | |
| 48 * Both the api_key and shared_secret are generally provided by the site | |
| 49 * offering OAuth services. You need to specify them at object creation | |
| 50 * because nobody <explative>ing uses OAuth without that minimal set of | |
| 51 * signatures. | |
| 52 * | |
| 53 * If you want to use the higher order security that comes from the | |
| 54 * OAuth token (sorry, I don't provide the functions to fetch that because | |
| 55 * sites aren't horribly consistent about how they offer that), you need to | |
| 56 * pass those in either with .setTokensAndSecrets() or as an argument to the | |
| 57 * .sign() or .getHeaderString() functions. | |
| 58 * | |
| 59 * Example: | |
| 60 <code> | |
| 61 var oauthObject = OAuthSimple().sign({path:'http://example.com/rest/', | |
| 62 parameters: 'foo=bar&gorp=banana', | |
| 63 signatures:{ | |
| 64 api_key:'12345abcd', | |
| 65 shared_secret:'xyz-5309' | |
| 66 }}); | |
| 67 document.getElementById('someLink').href=oauthObject.signed_url; | |
| 68 </code> | |
| 69 * | |
| 70 * that will sign as a "GET" using "SHA1-MAC" the url. If you need more than | |
| 71 * that, read on, McDuff. | |
| 72 */ | |
| 73 | |
| 74 /** OAuthSimple creator | |
| 75 * | |
| 76 * Create an instance of OAuthSimple | |
| 77 * | |
| 78 * @param api_key {string} The API Key (sometimes referred to as the c onsumer key) This value is usually supplied by the site you wish to use. | |
| 79 * @param shared_secret (string) The shared secret. This value is also usual ly provided by the site you wish to use. | |
| 80 */ | |
| 81 OAuthSimple = function (consumer_key,shared_secret) | |
| 82 { | |
| 83 /* if (api_key == undefined) | |
| 84 throw("Missing argument: api_key (oauth_consumer_key) for OAuthSimpl e. This is usually provided by the hosting site."); | |
| 85 if (shared_secret == undefined) | |
| 86 throw("Missing argument: shared_secret (shared secret) for OAuthSimp le. This is usually provided by the hosting site."); | |
| 87 */ this._secrets={}; | |
| 88 this._parameters={}; | |
| 89 | |
| 90 // General configuration options. | |
| 91 if (consumer_key !== undefined) { | |
| 92 this._secrets['consumer_key'] = consumer_key; | |
| 93 } | |
| 94 if (shared_secret !== undefined) { | |
| 95 this._secrets['shared_secret'] = shared_secret; | |
| 96 } | |
| 97 this._default_signature_method= "HMAC-SHA1"; | |
| 98 this._action = "GET"; | |
| 99 this._nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmno pqrstuvwxyz"; | |
| 100 | |
| 101 | |
| 102 this.reset = function() { | |
| 103 this._parameters={}; | |
| 104 this._path=undefined; | |
| 105 return this; | |
| 106 }; | |
| 107 | |
| 108 /** set the parameters either from a hash or a string | |
| 109 * | |
| 110 * @param {string,object} List of parameters for the call, this can eith er be a URI string (e.g. "foo=bar&gorp=banana" or an object/hash) | |
| 111 */ | |
| 112 this.setParameters = function (parameters) { | |
| 113 if (parameters === undefined) { | |
| 114 parameters = {}; | |
| 115 } | |
| 116 if (typeof(parameters) == 'string') { | |
| 117 parameters=this._parseParameterString(parameters); | |
| 118 } | |
| 119 this._parameters = parameters; | |
| 120 if (this._parameters['oauth_nonce'] === undefined) { | |
| 121 this._getNonce(); | |
| 122 } | |
| 123 if (this._parameters['oauth_timestamp'] === undefined) { | |
| 124 this._getTimestamp(); | |
| 125 } | |
| 126 if (this._parameters['oauth_method'] === undefined) { | |
| 127 this.setSignatureMethod(); | |
| 128 } | |
| 129 if (this._parameters['oauth_consumer_key'] === undefined) { | |
| 130 this._getApiKey(); | |
| 131 } | |
| 132 if(this._parameters['oauth_token'] === undefined) { | |
| 133 this._getAccessToken(); | |
| 134 } | |
| 135 | |
| 136 return this; | |
| 137 }; | |
| 138 | |
| 139 /** convienence method for setParameters | |
| 140 * | |
| 141 * @param parameters {string,object} See .setParameters | |
| 142 */ | |
| 143 this.setQueryString = function (parameters) { | |
| 144 return this.setParameters(parameters); | |
| 145 }; | |
| 146 | |
| 147 /** Set the target URL (does not include the parameters) | |
| 148 * | |
| 149 * @param path {string} the fully qualified URI (excluding query argumen ts) (e.g "http://example.org/foo") | |
| 150 */ | |
| 151 this.setURL = function (path) { | |
| 152 if (path == '') { | |
| 153 throw ('No path specified for OAuthSimple.setURL'); | |
| 154 } | |
| 155 this._path = path; | |
| 156 return this; | |
| 157 }; | |
| 158 | |
| 159 /** convienence method for setURL | |
| 160 * | |
| 161 * @param path {string} see .setURL | |
| 162 */ | |
| 163 this.setPath = function(path){ | |
| 164 return this.setURL(path); | |
| 165 }; | |
| 166 | |
| 167 /** set the "action" for the url, (e.g. GET,POST, DELETE, etc.) | |
| 168 * | |
| 169 * @param action {string} HTTP Action word. | |
| 170 */ | |
| 171 this.setAction = function(action) { | |
| 172 if (action === undefined) { | |
| 173 action="GET"; | |
| 174 } | |
| 175 action = action.toUpperCase(); | |
| 176 if (action.match('[^A-Z]')) { | |
| 177 throw ('Invalid action specified for OAuthSimple.setAction'); | |
| 178 } | |
| 179 this._action = action; | |
| 180 return this; | |
| 181 }; | |
| 182 | |
| 183 /** set the signatures (as well as validate the ones you have) | |
| 184 * | |
| 185 * @param signatures {object} object/hash of the token/signature pairs { api_key:, shared_secret:, oauth_token: oauth_secret:} | |
| 186 */ | |
| 187 this.setTokensAndSecrets = function(signatures) { | |
| 188 if (signatures) | |
| 189 { | |
| 190 for (var i in signatures) { | |
| 191 this._secrets[i] = signatures[i]; | |
| 192 } | |
| 193 } | |
| 194 // Aliases | |
| 195 if (this._secrets['api_key']) { | |
| 196 this._secrets.consumer_key = this._secrets.api_key; | |
| 197 } | |
| 198 if (this._secrets['access_token']) { | |
| 199 this._secrets.oauth_token = this._secrets.access_token; | |
| 200 } | |
| 201 if (this._secrets['access_secret']) { | |
| 202 this._secrets.oauth_secret = this._secrets.access_secret; | |
| 203 } | |
| 204 // Gauntlet | |
| 205 if (this._secrets.consumer_key === undefined) { | |
| 206 throw('Missing required consumer_key in OAuthSimple.setTokensAnd Secrets'); | |
| 207 } | |
| 208 if (this._secrets.shared_secret === undefined) { | |
| 209 throw('Missing required shared_secret in OAuthSimple.setTokensAn dSecrets'); | |
| 210 } | |
| 211 if ((this._secrets.oauth_token !== undefined) && (this._secrets.oaut h_secret === undefined)) { | |
| 212 throw('Missing oauth_secret for supplied oauth_token in OAuthSim ple.setTokensAndSecrets'); | |
| 213 } | |
| 214 return this; | |
| 215 }; | |
| 216 | |
| 217 /** set the signature method (currently only Plaintext or SHA-MAC1) | |
| 218 * | |
| 219 * @param method {string} Method of signing the transaction (only PLAINT EXT and SHA-MAC1 allowed for now) | |
| 220 */ | |
| 221 this.setSignatureMethod = function(method) { | |
| 222 if (method === undefined) { | |
| 223 method = this._default_signature_method; | |
| 224 } | |
| 225 //TODO: accept things other than PlainText or SHA-MAC1 | |
| 226 if (method.toUpperCase().match(/(PLAINTEXT|HMAC-SHA1)/) === undefine d) { | |
| 227 throw ('Unknown signing method specified for OAuthSimple.setSign atureMethod'); | |
| 228 } | |
| 229 this._parameters['oauth_signature_method']= method.toUpperCase(); | |
| 230 return this; | |
| 231 }; | |
| 232 | |
| 233 /** sign the request | |
| 234 * | |
| 235 * note: all arguments are optional, provided you've set them using the | |
| 236 * other helper functions. | |
| 237 * | |
| 238 * @param args {object} hash of arguments for the call | |
| 239 * {action:, path:, parameters:, method:, signatures:} | |
| 240 * all arguments are optional. | |
| 241 */ | |
| 242 this.sign = function (args) { | |
| 243 if (args === undefined) { | |
| 244 args = {}; | |
| 245 } | |
| 246 // Set any given parameters | |
| 247 if(args['action'] !== undefined) { | |
| 248 this.setAction(args['action']); | |
| 249 } | |
| 250 if (args['path'] !== undefined) { | |
| 251 this.setPath(args['path']); | |
| 252 } | |
| 253 if (args['method'] !== undefined) { | |
| 254 this.setSignatureMethod(args['method']); | |
| 255 } | |
| 256 this.setTokensAndSecrets(args['signatures']); | |
| 257 if (args['parameters'] !== undefined){ | |
| 258 this.setParameters(args['parameters']); | |
| 259 } | |
| 260 // check the parameters | |
| 261 var normParams = this._normalizedParameters(); | |
| 262 this._parameters['oauth_signature']=this._generateSignature(normPara ms); | |
| 263 return { | |
| 264 parameters: this._parameters, | |
| 265 signature: this._oauthEscape(this._parameters['oauth_signature'] ), | |
| 266 signed_url: this._path + '?' + this._normalizedParameters(), | |
| 267 header: this.getHeaderString() | |
| 268 }; | |
| 269 }; | |
| 270 | |
| 271 /** Return a formatted "header" string | |
| 272 * | |
| 273 * NOTE: This doesn't set the "Authorization: " prefix, which is require d. | |
| 274 * I don't set it because various set header functions prefer different | |
| 275 * ways to do that. | |
| 276 * | |
| 277 * @param args {object} see .sign | |
| 278 */ | |
| 279 this.getHeaderString = function(args) { | |
| 280 if (this._parameters['oauth_signature'] === undefined) { | |
| 281 this.sign(args); | |
| 282 } | |
| 283 | |
| 284 var result = 'OAuth '; | |
| 285 for (var pName in this._parameters) | |
| 286 { | |
| 287 if (!pName.match(/^oauth/)) { | |
| 288 continue; | |
| 289 } | |
| 290 if ((this._parameters[pName]) instanceof Array) | |
| 291 { | |
| 292 var pLength = this._parameters[pName].length; | |
| 293 for (var j=0;j<pLength;j++) | |
| 294 { | |
| 295 result += pName +'="'+this._oauthEscape(this._parameters [pName][j])+'" '; | |
| 296 } | |
| 297 } | |
| 298 else | |
| 299 { | |
| 300 result += pName + '="'+this._oauthEscape(this._parameters[pN ame])+'" '; | |
| 301 } | |
| 302 } | |
| 303 return result; | |
| 304 }; | |
| 305 | |
| 306 // Start Private Methods. | |
| 307 | |
| 308 /** convert the parameter string into a hash of objects. | |
| 309 * | |
| 310 */ | |
| 311 this._parseParameterString = function(paramString){ | |
| 312 var elements = paramString.split('&'); | |
| 313 var result={}; | |
| 314 for(var element=elements.shift();element;element=elements.shift()) | |
| 315 { | |
| 316 var keyToken=element.split('='); | |
| 317 var value=''; | |
| 318 if (keyToken[1]) { | |
| 319 value=decodeURIComponent(keyToken[1]); | |
| 320 } | |
| 321 if(result[keyToken[0]]){ | |
| 322 if (!(result[keyToken[0]] instanceof Array)) | |
| 323 { | |
| 324 result[keyToken[0]] = Array(result[keyToken[0]],value); | |
| 325 } | |
| 326 else | |
| 327 { | |
| 328 result[keyToken[0]].push(value); | |
| 329 } | |
| 330 } | |
| 331 else | |
| 332 { | |
| 333 result[keyToken[0]]=value; | |
| 334 } | |
| 335 } | |
| 336 return result; | |
| 337 }; | |
| 338 | |
| 339 this._oauthEscape = function(string) { | |
| 340 if (string === undefined) { | |
| 341 return ""; | |
| 342 } | |
| 343 if (string instanceof Array) | |
| 344 { | |
| 345 throw('Array passed to _oauthEscape'); | |
| 346 } | |
| 347 return encodeURIComponent(string).replace(/\!/g, "%21"). | |
| 348 replace(/\*/g, "%2A"). | |
| 349 replace(/'/g, "%27"). | |
| 350 replace(/\(/g, "%28"). | |
| 351 replace(/\)/g, "%29"); | |
| 352 }; | |
| 353 | |
| 354 this._getNonce = function (length) { | |
| 355 if (length === undefined) { | |
| 356 length=5; | |
| 357 } | |
| 358 var result = ""; | |
| 359 var cLength = this._nonce_chars.length; | |
| 360 for (var i = 0; i < length;i++) { | |
| 361 var rnum = Math.floor(Math.random() *cLength); | |
| 362 result += this._nonce_chars.substring(rnum,rnum+1); | |
| 363 } | |
| 364 this._parameters['oauth_nonce']=result; | |
| 365 return result; | |
| 366 }; | |
| 367 | |
| 368 this._getApiKey = function() { | |
| 369 if (this._secrets.consumer_key === undefined) { | |
| 370 throw('No consumer_key set for OAuthSimple.'); | |
| 371 } | |
| 372 this._parameters['oauth_consumer_key']=this._secrets.consumer_key; | |
| 373 return this._parameters.oauth_consumer_key; | |
| 374 }; | |
| 375 | |
| 376 this._getAccessToken = function() { | |
| 377 if (this._secrets['oauth_secret'] === undefined) { | |
| 378 return ''; | |
| 379 } | |
| 380 if (this._secrets['oauth_token'] === undefined) { | |
| 381 throw('No oauth_token (access_token) set for OAuthSimple.'); | |
| 382 } | |
| 383 this._parameters['oauth_token'] = this._secrets.oauth_token; | |
| 384 return this._parameters.oauth_token; | |
| 385 }; | |
| 386 | |
| 387 this._getTimestamp = function() { | |
| 388 var d = new Date(); | |
| 389 var ts = Math.floor(d.getTime()/1000); | |
| 390 this._parameters['oauth_timestamp'] = ts; | |
| 391 return ts; | |
| 392 }; | |
| 393 | |
| 394 this.b64_hmac_sha1 = function(k,d,_p,_z){ | |
| 395 // heavily optimized and compressed version of http://pajhome.org.uk/cry pt/md5/sha1.js | |
| 396 // _p = b64pad, _z = character size; not used here but I left them avail able just in case | |
| 397 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:-89 9497514;}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;}funct ion _h(k,d){var b=_b(k);if(b.length>16){b=_c(b,k.length*_z);}var p=[16],o=[16];f or(var i=0;i<16;i++){p[i]=b[i]^0x36363636;o[i]=b[i]^0x5C5C5C5C;}var h=_c(p.conca t(_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;}func tion _x(k,d){return _n(_h(k,d));}return _x(k,d); | |
|
Jamie
2011/05/11 20:41:04
I think this line might be a tad over the 80-chara
| |
| 398 } | |
| 399 | |
| 400 | |
| 401 this._normalizedParameters = function() { | |
| 402 var elements = new Array(); | |
| 403 var paramNames = []; | |
| 404 var ra =0; | |
| 405 for (var paramName in this._parameters) | |
| 406 { | |
| 407 if (ra++ > 1000) { | |
| 408 throw('runaway 1'); | |
| 409 } | |
| 410 paramNames.unshift(paramName); | |
| 411 } | |
| 412 paramNames = paramNames.sort(); | |
| 413 pLen = paramNames.length; | |
| 414 for (var i=0;i<pLen; i++) | |
| 415 { | |
| 416 paramName=paramNames[i]; | |
| 417 //skip secrets. | |
| 418 if (paramName.match(/\w+_secret/)) { | |
| 419 continue; | |
| 420 } | |
| 421 if (this._parameters[paramName] instanceof Array) | |
| 422 { | |
| 423 var sorted = this._parameters[paramName].sort(); | |
| 424 var spLen = sorted.length; | |
| 425 for (var j = 0;j<spLen;j++){ | |
| 426 if (ra++ > 1000) { | |
| 427 throw('runaway 1'); | |
| 428 } | |
| 429 elements.push(this._oauthEscape(paramName) + '=' + | |
| 430 this._oauthEscape(sorted[j])); | |
| 431 } | |
| 432 continue; | |
| 433 } | |
| 434 elements.push(this._oauthEscape(paramName) + '=' + | |
| 435 this._oauthEscape(this._parameters[paramName])); | |
| 436 } | |
| 437 return elements.join('&'); | |
| 438 }; | |
| 439 | |
| 440 this._generateSignature = function() { | |
| 441 | |
| 442 var secretKey = this._oauthEscape(this._secrets.shared_secret)+'&'+ | |
| 443 this._oauthEscape(this._secrets.oauth_secret); | |
| 444 if (this._parameters['oauth_signature_method'] == 'PLAINTEXT') | |
| 445 { | |
| 446 return secretKey; | |
| 447 } | |
| 448 if (this._parameters['oauth_signature_method'] == 'HMAC-SHA1') | |
| 449 { | |
| 450 var sigString = this._oauthEscape(this._action)+'&'+this._oauthE scape(this._path)+'&'+this._oauthEscape(this._normalizedParameters()); | |
| 451 return this.b64_hmac_sha1(secretKey,sigString); | |
| 452 } | |
| 453 return null; | |
| 454 }; | |
| 455 | |
| 456 return this; | |
| 457 }; | |
| 458 } | |
| OLD | NEW |