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 |