Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(176)

Side by Side Diff: remoting/webapp/me2mom/chrome_ex_oauth.js

Issue 7016001: Simple OAuth1 implementation based on http://code.google.com/chrome/extensions/tut_oauth.html. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 /**
2 * Copyright (c) 2010 The Chromium Authors. All rights reserved. Use of this
3 * source code is governed by a BSD-style license that can be found in the
4 * LICENSE file.
5 */
6
7 /**
8 * Constructor - no need to invoke directly, call initBackgroundPage instead.
9 * @constructor
10 * @param {String} url_request_token The OAuth request token URL.
11 * @param {String} url_auth_token The OAuth authorize token URL.
12 * @param {String} url_access_token The OAuth access token URL.
13 * @param {String} consumer_key The OAuth consumer key.
14 * @param {String} consumer_secret The OAuth consumer secret.
15 * @param {String} oauth_scope The OAuth scope parameter.
16 * @param {Object} opt_args Optional arguments. Recognized parameters:
17 * "app_name" {String} Name of the current application
18 * "callback_page" {String} If you renamed chrome_ex_oauth.html, the name
19 * this file was renamed to.
20 */
21 function ChromeExOAuth(url_request_token, url_auth_token, url_access_token,
22 consumer_key, consumer_secret, oauth_scope, opt_args) {
23 this.url_request_token = url_request_token;
24 this.url_auth_token = url_auth_token;
25 this.url_access_token = url_access_token;
26 this.consumer_key = consumer_key;
27 this.consumer_secret = consumer_secret;
28 this.oauth_scope = oauth_scope;
29 this.app_name = opt_args && opt_args['app_name'] ||
30 "ChromeExOAuth Library";
31 this.key_token = "oauth_token";
32 this.key_token_secret = "oauth_token_secret";
33 this.callback_page = opt_args && opt_args['callback_page'] ||
34 "chrome_ex_oauth.html";
35 this.auth_params = {};
36 if (opt_args && opt_args['auth_params']) {
37 for (key in opt_args['auth_params']) {
38 if (opt_args['auth_params'].hasOwnProperty(key)) {
39 this.auth_params[key] = opt_args['auth_params'][key];
40 }
41 }
42 }
43 };
44
45 /*******************************************************************************
46 * PUBLIC API METHODS
47 * Call these from your background page.
48 ******************************************************************************/
49
50 /**
51 * Initializes the OAuth helper from the background page. You must call this
52 * before attempting to make any OAuth calls.
53 * @param {Object} oauth_config Configuration parameters in a JavaScript object.
54 * The following parameters are recognized:
55 * "request_url" {String} OAuth request token URL.
56 * "authorize_url" {String} OAuth authorize token URL.
57 * "access_url" {String} OAuth access token URL.
58 * "consumer_key" {String} OAuth consumer key.
59 * "consumer_secret" {String} OAuth consumer secret.
60 * "scope" {String} OAuth access scope.
61 * "app_name" {String} Application name.
62 * "auth_params" {Object} Additional parameters to pass to the
63 * Authorization token URL. For an example, 'hd', 'hl', 'btmpl':
64 * http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth
65 * @return {ChromeExOAuth} An initialized ChromeExOAuth object.
66 */
67 ChromeExOAuth.initBackgroundPage = function(oauth_config) {
68 window.chromeExOAuthConfig = oauth_config;
69 window.chromeExOAuth = ChromeExOAuth.fromConfig(oauth_config);
70 window.chromeExOAuthRedirectStarted = false;
71 window.chromeExOAuthRequestingAccess = false;
72
73 var url_match = chrome.extension.getURL(window.chromeExOAuth.callback_page);
74 var tabs = {};
75 chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
76 if (changeInfo.url &&
77 changeInfo.url.substr(0, url_match.length) === url_match &&
78 changeInfo.url != tabs[tabId] &&
79 window.chromeExOAuthRequestingAccess == false) {
80 chrome.tabs.create({ 'url' : changeInfo.url }, function(tab) {
81 tabs[tab.id] = tab.url;
82 chrome.tabs.remove(tabId);
83 });
84 }
85 });
86
87 return window.chromeExOAuth;
88 };
89
90 /**
91 * Authorizes the current user with the configued API. You must call this
92 * before calling sendSignedRequest.
93 * @param {Function} callback A function to call once an access token has
94 * been obtained. This callback will be passed the following arguments:
95 * token {String} The OAuth access token.
96 * secret {String} The OAuth access token secret.
97 */
98 ChromeExOAuth.prototype.authorize = function(callback) {
99 if (this.hasToken()) {
100 callback(this.getToken(), this.getTokenSecret());
101 } else {
102 window.chromeExOAuthOnAuthorize = function(token, secret) {
103 callback(token, secret);
104 };
105 chrome.tabs.create({ 'url' :chrome.extension.getURL(this.callback_page) });
106 }
107 };
108
109 /**
110 * Clears any OAuth tokens stored for this configuration. Effectively a
111 * "logout" of the configured OAuth API.
112 */
113 ChromeExOAuth.prototype.clearTokens = function() {
114 delete localStorage[this.key_token + encodeURI(this.oauth_scope)];
115 delete localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
116 };
117
118 /**
119 * Returns whether a token is currently stored for this configuration.
120 * Effectively a check to see whether the current user is "logged in" to
121 * the configured OAuth API.
122 * @return {Boolean} True if an access token exists.
123 */
124 ChromeExOAuth.prototype.hasToken = function() {
125 return !!this.getToken();
126 };
127
128 /**
129 * Makes an OAuth-signed HTTP request with the currently authorized tokens.
130 * @param {String} url The URL to send the request to. Querystring parameters
131 * should be omitted.
132 * @param {Function} callback A function to be called once the request is
133 * completed. This callback will be passed the following arguments:
134 * responseText {String} The text response.
135 * xhr {XMLHttpRequest} The XMLHttpRequest object which was used to
136 * send the request. Useful if you need to check response status
137 * code, etc.
138 * @param {Object} opt_params Additional parameters to configure the request.
139 * The following parameters are accepted:
140 * "method" {String} The HTTP method to use. Defaults to "GET".
141 * "body" {String} A request body to send. Defaults to null.
142 * "parameters" {Object} Query parameters to include in the request.
143 * "headers" {Object} Additional headers to include in the request.
144 */
145 ChromeExOAuth.prototype.sendSignedRequest = function(url, callback,
146 opt_params) {
147 var method = opt_params && opt_params['method'] || 'GET';
148 var body = opt_params && opt_params['body'] || null;
149 var params = opt_params && opt_params['parameters'] || {};
150 var headers = opt_params && opt_params['headers'] || {};
151
152 var signedUrl = this.signURL(url, method, params);
153
154 ChromeExOAuth.sendRequest(method, signedUrl, headers, body, function (xhr) {
155 if (xhr.readyState == 4) {
156 callback(xhr.responseText, xhr);
157 }
158 });
159 };
160
161 /**
162 * Adds the required OAuth parameters to the given url and returns the
163 * result. Useful if you need a signed url but don't want to make an XHR
164 * request.
165 * @param {String} method The http method to use.
166 * @param {String} url The base url of the resource you are querying.
167 * @param {Object} opt_params Query parameters to include in the request.
168 * @return {String} The base url plus any query params plus any OAuth params.
169 */
170 ChromeExOAuth.prototype.signURL = function(url, method, opt_params) {
171 var token = this.getToken();
172 var secret = this.getTokenSecret();
173 if (!token || !secret) {
174 throw new Error("No oauth token or token secret");
175 }
176
177 var params = opt_params || {};
178
179 var result = OAuthSimple().sign({
180 action : method,
181 path : url,
182 parameters : params,
183 signatures: {
184 consumer_key : this.consumer_key,
185 shared_secret : this.consumer_secret,
186 oauth_secret : secret,
187 oauth_token: token
188 }
189 });
190
191 return result.signed_url;
192 };
193
194 /**
195 * Generates the Authorization header based on the oauth parameters.
196 * @param {String} url The base url of the resource you are querying.
197 * @param {Object} opt_params Query parameters to include in the request.
198 * @return {String} An Authorization header containing the oauth_* params.
199 */
200 ChromeExOAuth.prototype.getAuthorizationHeader = function(url, method,
201 opt_params) {
202 var token = this.getToken();
203 var secret = this.getTokenSecret();
204 if (!token || !secret) {
205 throw new Error("No oauth token or token secret");
206 }
207
208 var params = opt_params || {};
209
210 return OAuthSimple().getHeaderString({
211 action: method,
212 path : url,
213 parameters : params,
214 signatures: {
215 consumer_key : this.consumer_key,
216 shared_secret : this.consumer_secret,
217 oauth_secret : secret,
218 oauth_token: token
219 }
220 });
221 };
222
223 /*******************************************************************************
224 * PRIVATE API METHODS
225 * Used by the library. There should be no need to call these methods directly.
226 ******************************************************************************/
227
228 /**
229 * Creates a new ChromeExOAuth object from the supplied configuration object.
230 * @param {Object} oauth_config Configuration parameters in a JavaScript object.
231 * The following parameters are recognized:
232 * "request_url" {String} OAuth request token URL.
233 * "authorize_url" {String} OAuth authorize token URL.
234 * "access_url" {String} OAuth access token URL.
235 * "consumer_key" {String} OAuth consumer key.
236 * "consumer_secret" {String} OAuth consumer secret.
237 * "scope" {String} OAuth access scope.
238 * "app_name" {String} Application name.
239 * "auth_params" {Object} Additional parameters to pass to the
240 * Authorization token URL. For an example, 'hd', 'hl', 'btmpl':
241 * http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth
242 * @return {ChromeExOAuth} An initialized ChromeExOAuth object.
243 */
244 ChromeExOAuth.fromConfig = function(oauth_config) {
245 return new ChromeExOAuth(
246 oauth_config['request_url'],
247 oauth_config['authorize_url'],
248 oauth_config['access_url'],
249 oauth_config['consumer_key'],
250 oauth_config['consumer_secret'],
251 oauth_config['scope'],
252 {
253 'app_name' : oauth_config['app_name'],
254 'auth_params' : oauth_config['auth_params']
255 }
256 );
257 };
258
259 /**
260 * Initializes chrome_ex_oauth.html and redirects the page if needed to start
261 * the OAuth flow. Once an access token is obtained, this function closes
262 * chrome_ex_oauth.html.
263 */
264 ChromeExOAuth.initCallbackPage = function() {
265 var background_page = chrome.extension.getBackgroundPage();
266 var oauth_config = background_page.chromeExOAuthConfig;
267 var oauth = ChromeExOAuth.fromConfig(oauth_config);
268 background_page.chromeExOAuthRedirectStarted = true;
269 oauth.initOAuthFlow(function (token, secret) {
270 background_page.chromeExOAuthOnAuthorize(token, secret);
271 background_page.chromeExOAuthRedirectStarted = false;
272 chrome.tabs.getSelected(null, function (tab) {
273 chrome.tabs.remove(tab.id);
274 });
275 });
276 };
277
278 /**
279 * Sends an HTTP request. Convenience wrapper for XMLHttpRequest calls.
280 * @param {String} method The HTTP method to use.
281 * @param {String} url The URL to send the request to.
282 * @param {Object} headers Optional request headers in key/value format.
283 * @param {String} body Optional body content.
284 * @param {Function} callback Function to call when the XMLHttpRequest's
285 * ready state changes. See documentation for XMLHttpRequest's
286 * onreadystatechange handler for more information.
287 */
288 ChromeExOAuth.sendRequest = function(method, url, headers, body, callback) {
289 var xhr = new XMLHttpRequest();
290 xhr.onreadystatechange = function(data) {
291 callback(xhr, data);
292 }
293 xhr.open(method, url, true);
294 if (headers) {
295 for (var header in headers) {
296 if (headers.hasOwnProperty(header)) {
297 xhr.setRequestHeader(header, headers[header]);
298 }
299 }
300 }
301 xhr.send(body);
302 };
303
304 /**
305 * Decodes a URL-encoded string into key/value pairs.
306 * @param {String} encoded An URL-encoded string.
307 * @return {Object} An object representing the decoded key/value pairs found
308 * in the encoded string.
309 */
310 ChromeExOAuth.formDecode = function(encoded) {
311 var params = encoded.split("&");
312 var decoded = {};
313 for (var i = 0, param; param = params[i]; i++) {
314 var keyval = param.split("=");
315 if (keyval.length == 2) {
316 var key = ChromeExOAuth.fromRfc3986(keyval[0]);
317 var val = ChromeExOAuth.fromRfc3986(keyval[1]);
318 decoded[key] = val;
319 }
320 }
321 return decoded;
322 };
323
324 /**
325 * Returns the current window's querystring decoded into key/value pairs.
326 * @return {Object} A object representing any key/value pairs found in the
327 * current window's querystring.
328 */
329 ChromeExOAuth.getQueryStringParams = function() {
330 var urlparts = window.location.href.split("?");
331 if (urlparts.length >= 2) {
332 var querystring = urlparts.slice(1).join("?");
333 return ChromeExOAuth.formDecode(querystring);
334 }
335 return {};
336 };
337
338 /**
339 * Binds a function call to a specific object. This function will also take
340 * a variable number of additional arguments which will be prepended to the
341 * arguments passed to the bound function when it is called.
342 * @param {Function} func The function to bind.
343 * @param {Object} obj The object to bind to the function's "this".
344 * @return {Function} A closure that will call the bound function.
345 */
346 ChromeExOAuth.bind = function(func, obj) {
347 var newargs = Array.prototype.slice.call(arguments).slice(2);
348 return function() {
349 var combinedargs = newargs.concat(Array.prototype.slice.call(arguments));
350 func.apply(obj, combinedargs);
351 };
352 };
353
354 /**
355 * Encodes a value according to the RFC3986 specification.
356 * @param {String} val The string to encode.
357 */
358 ChromeExOAuth.toRfc3986 = function(val){
359 return encodeURIComponent(val)
360 .replace(/\!/g, "%21")
361 .replace(/\*/g, "%2A")
362 .replace(/'/g, "%27")
363 .replace(/\(/g, "%28")
364 .replace(/\)/g, "%29");
365 };
366
367 /**
368 * Decodes a string that has been encoded according to RFC3986.
369 * @param {String} val The string to decode.
370 */
371 ChromeExOAuth.fromRfc3986 = function(val){
372 var tmp = val
373 .replace(/%21/g, "!")
374 .replace(/%2A/g, "*")
375 .replace(/%27/g, "'")
376 .replace(/%28/g, "(")
377 .replace(/%29/g, ")");
378 return decodeURIComponent(tmp);
379 };
380
381 /**
382 * Adds a key/value parameter to the supplied URL.
383 * @param {String} url An URL which may or may not contain querystring values.
384 * @param {String} key A key
385 * @param {String} value A value
386 * @return {String} The URL with URL-encoded versions of the key and value
387 * appended, prefixing them with "&" or "?" as needed.
388 */
389 ChromeExOAuth.addURLParam = function(url, key, value) {
390 var sep = (url.indexOf('?') >= 0) ? "&" : "?";
391 return url + sep +
392 ChromeExOAuth.toRfc3986(key) + "=" + ChromeExOAuth.toRfc3986(value);
393 };
394
395 /**
396 * Stores an OAuth token for the configured scope.
397 * @param {String} token The token to store.
398 */
399 ChromeExOAuth.prototype.setToken = function(token) {
400 localStorage[this.key_token + encodeURI(this.oauth_scope)] = token;
401 };
402
403 /**
404 * Retrieves any stored token for the configured scope.
405 * @return {String} The stored token.
406 */
407 ChromeExOAuth.prototype.getToken = function() {
408 return localStorage[this.key_token + encodeURI(this.oauth_scope)];
409 };
410
411 /**
412 * Stores an OAuth token secret for the configured scope.
413 * @param {String} secret The secret to store.
414 */
415 ChromeExOAuth.prototype.setTokenSecret = function(secret) {
416 localStorage[this.key_token_secret + encodeURI(this.oauth_scope)] = secret;
417 };
418
419 /**
420 * Retrieves any stored secret for the configured scope.
421 * @return {String} The stored secret.
422 */
423 ChromeExOAuth.prototype.getTokenSecret = function() {
424 return localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
425 };
426
427 /**
428 * Starts an OAuth authorization flow for the current page. If a token exists,
429 * no redirect is needed and the supplied callback is called immediately.
430 * If this method detects that a redirect has finished, it grabs the
431 * appropriate OAuth parameters from the URL and attempts to retrieve an
432 * access token. If no token exists and no redirect has happened, then
433 * an access token is requested and the page is ultimately redirected.
434 * @param {Function} callback The function to call once the flow has finished.
435 * This callback will be passed the following arguments:
436 * token {String} The OAuth access token.
437 * secret {String} The OAuth access token secret.
438 */
439 ChromeExOAuth.prototype.initOAuthFlow = function(callback) {
440 if (!this.hasToken()) {
441 var params = ChromeExOAuth.getQueryStringParams();
442 if (params['chromeexoauthcallback'] == 'true') {
443 var oauth_token = params['oauth_token'];
444 var oauth_verifier = params['oauth_verifier']
445 this.getAccessToken(oauth_token, oauth_verifier, callback);
446 } else {
447 var request_params = {
448 'url_callback_param' : 'chromeexoauthcallback'
449 }
450 this.getRequestToken(function(url) {
451 window.location.href = url;
452 }, request_params);
453 }
454 } else {
455 callback(this.getToken(), this.getTokenSecret());
456 }
457 };
458
459 /**
460 * Requests an OAuth request token.
461 * @param {Function} callback Function to call once the authorize URL is
462 * calculated. This callback will be passed the following arguments:
463 * url {String} The URL the user must be redirected to in order to
464 * approve the token.
465 * @param {Object} opt_args Optional arguments. The following parameters
466 * are accepted:
467 * "url_callback" {String} The URL the OAuth provider will redirect to.
468 * "url_callback_param" {String} A parameter to include in the callback
469 * URL in order to indicate to this library that a redirect has
470 * taken place.
471 */
472 ChromeExOAuth.prototype.getRequestToken = function(callback, opt_args) {
473 if (typeof callback !== "function") {
474 throw new Error("Specified callback must be a function.");
475 }
476 var url = opt_args && opt_args['url_callback'] ||
477 window && window.top && window.top.location &&
478 window.top.location.href;
479
480 var url_param = opt_args && opt_args['url_callback_param'] ||
481 "chromeexoauthcallback";
482 var url_callback = ChromeExOAuth.addURLParam(url, url_param, "true");
483
484 var result = OAuthSimple().sign({
485 path : this.url_request_token,
486 parameters: {
487 "xoauth_displayname" : this.app_name,
488 "scope" : this.oauth_scope,
489 "oauth_callback" : url_callback
490 },
491 signatures: {
492 consumer_key : this.consumer_key,
493 shared_secret : this.consumer_secret
494 }
495 });
496 var onToken = ChromeExOAuth.bind(this.onRequestToken, this, callback);
497 ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken);
498 };
499
500 /**
501 * Called when a request token has been returned. Stores the request token
502 * secret for later use and sends the authorization url to the supplied
503 * callback (for redirecting the user).
504 * @param {Function} callback Function to call once the authorize URL is
505 * calculated. This callback will be passed the following arguments:
506 * url {String} The URL the user must be redirected to in order to
507 * approve the token.
508 * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
509 * request token.
510 */
511 ChromeExOAuth.prototype.onRequestToken = function(callback, xhr) {
512 if (xhr.readyState == 4) {
513 if (xhr.status == 200) {
514 var params = ChromeExOAuth.formDecode(xhr.responseText);
515 var token = params['oauth_token'];
516 this.setTokenSecret(params['oauth_token_secret']);
517 var url = ChromeExOAuth.addURLParam(this.url_auth_token,
518 "oauth_token", token);
519 for (var key in this.auth_params) {
520 if (this.auth_params.hasOwnProperty(key)) {
521 url = ChromeExOAuth.addURLParam(url, key, this.auth_params[key]);
522 }
523 }
524 callback(url);
525 } else {
526 throw new Error("Fetching request token failed. Status " + xhr.status);
527 }
528 }
529 };
530
531 /**
532 * Requests an OAuth access token.
533 * @param {String} oauth_token The OAuth request token.
534 * @param {String} oauth_verifier The OAuth token verifier.
535 * @param {Function} callback The function to call once the token is obtained.
536 * This callback will be passed the following arguments:
537 * token {String} The OAuth access token.
538 * secret {String} The OAuth access token secret.
539 */
540 ChromeExOAuth.prototype.getAccessToken = function(oauth_token, oauth_verifier,
541 callback) {
542 if (typeof callback !== "function") {
543 throw new Error("Specified callback must be a function.");
544 }
545 var bg = chrome.extension.getBackgroundPage();
546 if (bg.chromeExOAuthRequestingAccess == false) {
547 bg.chromeExOAuthRequestingAccess = true;
548
549 var result = OAuthSimple().sign({
550 path : this.url_access_token,
551 parameters: {
552 "oauth_token" : oauth_token,
553 "oauth_verifier" : oauth_verifier
554 },
555 signatures: {
556 consumer_key : this.consumer_key,
557 shared_secret : this.consumer_secret,
558 oauth_secret : this.getTokenSecret(this.oauth_scope)
559 }
560 });
561
562 var onToken = ChromeExOAuth.bind(this.onAccessToken, this, callback);
563 ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken);
564 }
565 };
566
567 /**
568 * Called when an access token has been returned. Stores the access token and
569 * access token secret for later use and sends them to the supplied callback.
570 * @param {Function} callback The function to call once the token is obtained.
571 * This callback will be passed the following arguments:
572 * token {String} The OAuth access token.
573 * secret {String} The OAuth access token secret.
574 * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
575 * access token.
576 */
577 ChromeExOAuth.prototype.onAccessToken = function(callback, xhr) {
578 if (xhr.readyState == 4) {
579 var bg = chrome.extension.getBackgroundPage();
580 if (xhr.status == 200) {
581 var params = ChromeExOAuth.formDecode(xhr.responseText);
582 var token = params["oauth_token"];
583 var secret = params["oauth_token_secret"];
584 this.setToken(token);
585 this.setTokenSecret(secret);
586 bg.chromeExOAuthRequestingAccess = false;
587 callback(token, secret);
588 } else {
589 bg.chromeExOAuthRequestingAccess = false;
590 throw new Error("Fetching access token failed with status " + xhr.status);
591 }
592 }
593 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698