source: Dev/branches/rest-dojo-ui/client/dojox/io/OAuth.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 9.3 KB
Line 
1define([
2        "dojo/_base/kernel", // dojo
3        "dojo/_base/lang", // mixin
4        "dojo/_base/array", // isArray, map
5        "dojo/_base/xhr", // formToObject, queryToObject, xhr
6        "dojo/dom", // byId
7        "dojox/encoding/digests/SHA1", // SHA1
8], function(dojo, lang, array, xhr, dom, SHA1){
9dojo.getObject("io.OAuth", true, dojox);
10
11dojox.io.OAuth = new (function(){
12        //      summary:
13        //              Helper singleton for signing any kind of Ajax request using the OAuth 1.0 protocol.
14        //      description:
15        //              dojox.io.OAuth is a singleton class designed to allow anyone to sign a request,
16        //              based on the OAuth 1.0 specification, made with any of the Dojo Toolkit's Ajax
17        //              methods (such as dojo.xhr[verb], dojo.io.iframe, etc.).
18        //
19        //              The main method of dojox.io.OAuth is the sign method (see documentation for .sign);
20        //              the idea is that you will "sign" the kwArgs object you'd normally pass to any of
21        //              the Ajax methods, and then pass the signed object along.  As long as the token
22        //              object used is valid (and the client's date and time are synced with a public
23        //              time server), a signed object should be passed along correctly.
24        //
25        //              dojox.io.OAuth does not deal with the OAuth handshake process at all.
26        //
27        //              This object was developed against the Netflix API (OAuth-based service); see
28        //              http://developer.netflix.com for more details.
29        var encode = this.encode = function(s){
30                if(!("" + s).length){ return ""; }
31                return encodeURIComponent(s)
32                        .replace(/\!/g, "%21")
33                        .replace(/\*/g, "%2A")
34                        .replace(/\'/g, "%27")
35                        .replace(/\(/g, "%28")
36                        .replace(/\)/g, "%29");
37        };
38
39        var decode = this.decode = function(str){
40                //      summary:
41                //              Break apart the passed string and decode.
42                //              Some special cases are handled.
43                var a=[], list=str.split("&");
44                for(var i=0, l=list.length; i<l; i++){
45                        var item=list[i];
46                        if(list[i]==""){ continue; }    //      skip this one.
47                        if(list[i].indexOf("=")>-1){
48                                var tmp=list[i].split("=");
49                                a.push([ decodeURIComponent(tmp[0]), decodeURIComponent(tmp[1]) ]);
50                        } else {
51                                a.push([ decodeURIComponent(list[i]), null ]);
52                        }
53                }
54                return a;
55        };
56
57        function parseUrl(url){
58                //      summary:
59                //              Create a map out of the passed URL.  Need to pull any
60                //              query string parameters off the URL for the base signature string.
61        var keys = [
62                                "source","protocol","authority","userInfo",
63                                "user","password","host","port",
64                                "relative","path","directory",
65                                "file","query","anchor"
66                        ],
67                        parser=/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
68                        match=parser.exec(url),
69                        map = {},
70                        i=keys.length;
71
72                //      create the base map first.
73                while(i--){ map[keys[i]] = match[i] || ""; }
74
75                //      create the normalized version of the url and add it to the map
76                var p=map.protocol.toLowerCase(),
77                        a=map.authority.toLowerCase(),
78                        b=(p=="http"&&map.port==80)||(p=="https"&&map.port==443);
79                if(b){
80                        if(a.lastIndexOf(":")>-1){
81                                a=a.substring(0, a.lastIndexOf(":"));
82                        }
83                }
84                var path=map.path||"/";
85                map.url=p+"://"+a+path;
86
87                //      return the map
88                return map;
89        }
90
91        var tab="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
92        function nonce(length){
93                var s="", tl=tab.length;
94                for(var i=0; i<length; i++){
95                        s+=tab.charAt(Math.floor(Math.random()*tl));
96                }
97                return s;
98        }
99        function timestamp(){
100                return Math.floor(new Date().valueOf()/1000)-2;
101        }
102        function signature(data, key, type){
103                if(type && type!="PLAINTEXT" && type!="HMAC-SHA1"){
104                        throw new Error("dojox.io.OAuth: the only supported signature encodings are PLAINTEXT and HMAC-SHA1.");
105                }
106
107                if(type=="PLAINTEXT"){
108                        return key;
109                } else {
110                        //      assume SHA1 HMAC
111                        return SHA1._hmac(data, key);
112                }
113        }
114
115        function key(args){
116                //      summary:
117                //              return the key used to sign a message based on the token object.
118                return encode(args.consumer.secret)
119                        + "&"
120                        + (args.token && args.token.secret ? encode(args.token.secret) : "");
121        }
122
123        function addOAuth(/* dojo.__XhrArgs */args, /* dojox.io.__OAuthArgs */oaa){
124                //      summary:
125                //              Add the OAuth parameters to the query string/content.
126                var o = {
127                        oauth_consumer_key: oaa.consumer.key,
128                        oauth_nonce: nonce(16),
129                        oauth_signature_method: oaa.sig_method || "HMAC-SHA1",
130                        oauth_timestamp: timestamp(),
131                        oauth_version: "1.0"
132                }
133                if(oaa.token){
134                        o.oauth_token = oaa.token.key;
135                }
136                args.content = dojo.mixin(args.content||{}, o);
137        }
138
139        function convertArgs(args){
140                //      summary:
141                //              Because of the need to create a base string, we have to do
142                //              some manual args preparation instead of relying on the internal
143                //              Dojo xhr functions.  But we'll let dojo.xhr assemble things
144                //              as it normally would.
145                var miArgs = [{}], formObject;
146
147                if(args.form){
148                        if(!args.content){ args.content = {}; }
149                        var form = dojo.byId(args.form);
150                        var actnNode = form.getAttributeNode("action");
151                        args.url = args.url || (actnNode ? actnNode.value : null);
152                        formObject = dojo.formToObject(form);
153                        delete args.form;
154                }
155                if(formObject){ miArgs.push(formObject); }
156                if(args.content){ miArgs.push(args.content); }
157
158                //      pull anything off the query string
159                var map = parseUrl(args.url);
160                if(map.query){
161                        var tmp = dojo.queryToObject(map.query);
162                        //      re-encode the values.  sigh
163                        for(var p in tmp){ tmp[p] = encodeURIComponent(tmp[p]); }
164                        miArgs.push(tmp);
165                }
166                args._url = map.url;
167
168                //      now set up all the parameters as an array of 2 element arrays.
169                var a = [];
170                for(var i=0, l=miArgs.length; i<l; i++){
171                        var item=miArgs[i];
172                        for(var p in item){
173                                if(dojo.isArray(item[p])){
174                                        //      handle multiple values
175                                        for(var j=0, jl=item.length; j<jl; j++){
176                                                a.push([ p, item[j] ]);
177                                        }
178                                } else {
179                                        a.push([ p, item[p] ]);
180                                }
181                        }
182                }
183
184                args._parameters = a;
185                return args;
186        }
187
188        function baseString(/* String */method, /* dojo.__XhrArgs */args, /* dojox.io.__OAuthArgs */oaa){
189                //      create and return the base string out of the args.
190                addOAuth(args, oaa);
191                convertArgs(args);
192
193                var a = args._parameters;
194
195                //      sort the parameters
196                a.sort(function(a,b){
197                        if(a[0]>b[0]){ return 1; }
198                        if(a[0]<b[0]){ return -1; }
199                        if(a[1]>b[1]){ return 1; }
200                        if(a[1]<b[1]){ return -1; }
201                        return 0;
202                });
203
204                //      encode.
205                var s = dojo.map(a, function(item){
206                        return encode(item[0]) + "=" + encode((""+item[1]).length ? item[1] : "");
207                }).join("&");
208
209                var baseString = method.toUpperCase()
210                        + "&" + encode(args._url)
211                        + "&" + encode(s);
212                return baseString;
213        }
214
215        function sign(method, args, oaa){
216                //      return the oauth_signature for this message.
217                var k = key(oaa),
218                        message = baseString(method, args, oaa),
219                        s = signature(message, k, oaa.sig_method || "HMAC-SHA1");
220                args.content["oauth_signature"] = s;
221                return args;
222        }
223       
224        /*=====
225                dojox.io.OAuth.__AccessorArgs = function(key, secret){
226                        //      key: String
227                        //              The key or token issued to either the consumer or by the OAuth service.
228                        //      secret: String
229                        //              The secret (shared secret for consumers, issued secret by OAuth service).
230                        this.key = key;
231                        this.secret = secret;
232                };
233                dojox.io.OAuth.__OAuthArgs = function(consumer, sig_method, token){
234                        //      consumer: dojox.io.OAuth.__AccessorArgs
235                        //              The consumer information issued to your OpenAuth application.
236                        //      sig_method: String
237                        //              The method used to create the signature.  Should be PLAINTEXT or HMAC-SHA1.
238                        //      token: dojox.io.OAuth.__AccessorArgs?
239                        //              The request token and secret issued by the OAuth service.  If not
240                        //              issued yet, this should be null.
241                        this.consumer = consumer;
242                        this.token = token;
243                }
244        =====*/
245
246        /*
247         *      Process goes something like this:
248         *      1. prepare the base string
249         *      2. create the key
250         *      3. create the signature based on the base string and the key
251         *      4. send the request using dojo.xhr[METHOD].
252         */
253
254        this.sign = function(/* String*/method, /* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs */oaa){
255                //      summary:
256                //              Given the OAuth access arguments, sign the kwArgs that you would pass
257                //              to any dojo Ajax method (dojo.xhr*, dojo.io.iframe, dojo.io.script).
258                //      example:
259                //              Sign the kwArgs object for use with dojo.xhrGet:
260                //      |       var oaa = {
261                //      |               consumer: {
262                //      |                       key: "foobar",
263                //      |                       secret: "barbaz"
264                //      |               }
265                //      |       };
266                //      |
267                //      |       var args = dojox.io.OAuth.sign("GET", myAjaxKwArgs, oaa);
268                //      |       dojo.xhrGet(args);
269                return sign(method, args, oaa);
270        };
271
272
273        //      TODO: handle redirect requests?
274        this.xhr = function(/* String */method, /* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs */oaa, /* Boolean? */hasBody){
275                /*      summary:
276                 *              Make an XHR request that is OAuth signed.
277                 *      example:
278                 *      |       var dfd = dojox.io.OAuth.xhrGet({
279                 *      |               url: "http://someauthdomain.com/path?foo=bar",
280                 *      |               load: function(response, ioArgs){ }
281                 *      |       },
282                 *      |       {
283                 *      |               consumer:{ key: "lasdkf9asdnfsdf", secret: "9asdnfskdfysjr" }
284                 *      |       });
285                 */
286                sign(method, args, oaa);
287                return xhr(method, args, hasBody);
288        };
289
290        this.xhrGet = function(/* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs*/ oaa){
291                return this.xhr("GET", args, oaa);
292        };
293        this.xhrPost = this.xhrRawPost = function(/* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs*/ oaa){
294                return this.xhr("POST", args, oaa, true);
295        };
296        this.xhrPut = this.xhrRawPut = function(/* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs*/ oaa){
297                return this.xhr("PUT", args, oaa, true);
298        };
299        this.xhrDelete = function(/* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs*/ oaa){
300                return this.xhr("DELETE", args, oaa);
301        };
302})();
303
304return dojox.io.OAuth;
305
306});
Note: See TracBrowser for help on using the repository browser.