diff options
author | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-20 01:38:42 +0000 |
---|---|---|
committer | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-20 01:38:42 +0000 |
commit | 0a0d6cc68ed093cd31e1ebd4fedb453f7b6df127 (patch) | |
tree | 5ea5090db4bcf7dfc242ab66c1770954ac005433 /remoting | |
parent | 15a245c06daf2b80475d3121e3be3e261b582486 (diff) | |
download | chromium_src-0a0d6cc68ed093cd31e1ebd4fedb453f7b6df127.zip chromium_src-0a0d6cc68ed093cd31e1ebd4fedb453f7b6df127.tar.gz chromium_src-0a0d6cc68ed093cd31e1ebd4fedb453f7b6df127.tar.bz2 |
Basic OAuth2 support using the native app flow.
Currently, OAuth2 does not support redirection to chrome-extension URLs. So,
temporarily, we will use the native app flow. In the future, this should be
replaced with the "PostMessage" flow.
BUG=none
TEST=Invoke javascript functions by hand and see that we get an access token.
Review URL: http://codereview.chromium.org/7046012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@86034 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/tools/keygen.py | 22 | ||||
-rw-r--r-- | remoting/webapp/me2mom/background.html | 2 | ||||
-rw-r--r-- | remoting/webapp/me2mom/background.js | 10 | ||||
-rw-r--r-- | remoting/webapp/me2mom/choice.html | 57 | ||||
-rw-r--r-- | remoting/webapp/me2mom/chrome_ex_oauth.html | 27 | ||||
-rw-r--r-- | remoting/webapp/me2mom/chrome_ex_oauth.js | 593 | ||||
-rw-r--r-- | remoting/webapp/me2mom/chrome_ex_oauthsimple.js | 458 | ||||
-rw-r--r-- | remoting/webapp/me2mom/manifest.json | 1 | ||||
-rw-r--r-- | remoting/webapp/me2mom/oauth2.js | 138 | ||||
-rw-r--r-- | remoting/webapp/me2mom/remoting.js | 64 |
10 files changed, 233 insertions, 1139 deletions
diff --git a/remoting/tools/keygen.py b/remoting/tools/keygen.py index bdc488c..deaeae1 100644 --- a/remoting/tools/keygen.py +++ b/remoting/tools/keygen.py @@ -1,4 +1,4 @@ -# Copyright (c) 2010 The Chromium Authors. All rights reserved. +# Copyright (c) 2011 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -10,14 +10,14 @@ if SCRIPT_PATH == "": SCRIPT_PATH = os.getcwd() PATHS_TO_TRY = [ - '\\..\\..\\build\\Debug\\chromoting_host_keygen.exe', - '\\..\\..\\build\\Release\\chromoting_host_keygen.exe', - '\\..\\Debug\\chromoting_host_keygen.exe', - '\\..\\Release\\chromoting_host_keygen.exe', - '/../../xcodebuild/Debug/chromoting_host_keygen', - '/../../xcodebuild/Release/chromoting_host_keygen', - '/../../out/Debug/chromoting_host_keygen', - '/../../out/Release/chromoting_host_keygen'] + '\\..\\..\\build\\Debug\\remoting_host_keygen.exe', + '\\..\\..\\build\\Release\\remoting_host_keygen.exe', + '\\..\\Debug\\remoting_host_keygen.exe', + '\\..\\Release\\remoting_host_keygen.exe', + '/../../xcodebuild/Debug/remoting_host_keygen', + '/../../xcodebuild/Release/remoting_host_keygen', + '/../../out/Debug/remoting_host_keygen', + '/../../out/Release/remoting_host_keygen'] KEYGEN_PATH = None for path in PATHS_TO_TRY: @@ -26,7 +26,7 @@ for path in PATHS_TO_TRY: break if not KEYGEN_PATH: - raise Exception("Unable to find chromoting_host_keygen. Please build it " + + raise Exception("Unable to find remoting_host_keygen. Please build it " + "and try again") def generateRSAKeyPair(): @@ -35,5 +35,5 @@ def generateRSAKeyPair(): pipe = os.popen(KEYGEN_PATH) out = pipe.readlines() if len(out) != 2: - raise Exception("chromoting_host_keygen failed.") + raise Exception("remoting_host_keygen failed.") return (out[0].strip(), out[1].strip()) diff --git a/remoting/webapp/me2mom/background.html b/remoting/webapp/me2mom/background.html index 3cadf1e..3859031 100644 --- a/remoting/webapp/me2mom/background.html +++ b/remoting/webapp/me2mom/background.html @@ -5,7 +5,5 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> <html> - <script type="text/javascript" src="chrome_ex_oauthsimple.js"></script> - <script type="text/javascript" src="chrome_ex_oauth.js"></script> <script type="text/javascript" src="background.js"></script> </html> diff --git a/remoting/webapp/me2mom/background.js b/remoting/webapp/me2mom/background.js index 8a59d19..0da714b 100644 --- a/remoting/webapp/me2mom/background.js +++ b/remoting/webapp/me2mom/background.js @@ -20,13 +20,3 @@ function removeItem(key) { function clearAll() { window.localStorage.clear(); } - -var oauth = ChromeExOAuth.initBackgroundPage({ - 'request_url': 'https://www.google.com/accounts/OAuthGetRequestToken', - 'authorize_url': 'https://www.google.com/accounts/OAuthAuthorizeToken', - 'access_url': 'https://www.google.com/accounts/OAuthGetAccessToken', - 'consumer_key': 'anonymous', - 'consumer_secret': 'anonymous', - 'scope': 'https://www.googleapis.com/auth/chromoting', - 'app_name': 'Remoting WebApp' -}); diff --git a/remoting/webapp/me2mom/choice.html b/remoting/webapp/me2mom/choice.html index 9011065..3c44172 100644 --- a/remoting/webapp/me2mom/choice.html +++ b/remoting/webapp/me2mom/choice.html @@ -10,6 +10,7 @@ found in the LICENSE file. <head> <link rel="stylesheet" type="text/css" href="main.css" /> <script type="text/javascript" src="remoting.js"></script> + <script type="text/javascript" src="oauth2.js"></script> <title>Select Role</title> </head> @@ -17,31 +18,45 @@ found in the LICENSE file. <!-- Auth panel --> <div id="auth_panel"> - Chromoting OAuth1 Token: <span id="oauth1_status"></span> - <button onclick="authorizeOAuth1();">Authorize</button> - <button onclick="clearOAuth1();">Clear</button > - <br /> - XMPP Token: <span id="xmpp_status"></span> - <button onclick="clearXmpp();" id="xmpp_clear" style="display:none;"> + OAuth2 Token: <span id="oauth2_status"></span> + <button onclick="remoting.oauth2.openOAuth2Window();" + id="oauth2_code_button"> + Open OAuth2 Window + </button> + <button onclick="clearOAuth2();" id="oauth2_clear_button"> Clear </button> - <form id='xmpp_form' action="" - onsubmit="authorizeXmpp(this); return false;"> - <label for="xmpp_username">Email:</label> - <input type="text" name="xmpp_username" id="xmpp_username" /> - <label for="xmpp_password">App-specific Password:</label> - <input type="password" name="xmpp_password" id="xmpp_password" /> - <div id="xmpp_captcha" style="display:none;"> - <img style="display:block;" id="xmpp_captcha_img" /> - <input type="hidden" name="xmpp_captcha_token" /> - <input type="text" name="xmpp_captcha_result" /> - </div> + <form id='oauth2_form' action="" + onsubmit="authorizeOAuth2(this['oauth2_code'].value); return false;" + style="display:none"> + <label for="auth2_code">OAuth2 Code (from window):</label> + <input type="text" name="oauth2_code" id="oauth2_code" /> <input type="submit"/> </form> - <span id="xmpp_last_error" style="display:none"></span> - <iframe id="xmpp_error" style="display:none"> - <p> No iframe support - </iframe> + <div id="xmpp_div"> + XMPP Token: <span id="xmpp_status"></span> + <button onclick="clearXmpp();" id="xmpp_clear" style="display:none;"> + Clear + </button> + + <form id='xmpp_form' action="" + onsubmit="authorizeXmpp(this); return false;"> + <label for="xmpp_username">Email:</label> + <input type="text" name="xmpp_username" id="xmpp_username" /> + <label for="xmpp_password">App-specific Password:</label> + <input type="password" name="xmpp_password" id="xmpp_password" /> + <div id="xmpp_captcha" style="display:none;"> + <img style="display:block;" id="xmpp_captcha_img" /> + <input type="hidden" name="xmpp_captcha_token" /> + <input type="text" name="xmpp_captcha_result" /> + </div> + <input type="submit"/> + </form> + <span id="xmpp_last_error" style="display:none"></span> + <iframe id="xmpp_error" style="display:none"> + <p> No iframe support + </iframe> + </div> </div> <!-- Host UI --> diff --git a/remoting/webapp/me2mom/chrome_ex_oauth.html b/remoting/webapp/me2mom/chrome_ex_oauth.html deleted file mode 100644 index 912f891..0000000 --- a/remoting/webapp/me2mom/chrome_ex_oauth.html +++ /dev/null @@ -1,27 +0,0 @@ -<!DOCTYPE html> -<!-- - * Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this - * source code is governed by a BSD-style license that can be found in the - * LICENSE file. ---> -<html> - <head> - <title>OAuth Redirect Page</title> - <style type="text/css"> - body { - font: 16px Arial; - color: #333; - } - </style> - <script type="text/javascript" src="chrome_ex_oauthsimple.js"></script> - <script type="text/javascript" src="chrome_ex_oauth.js"></script> - <script type="text/javascript"> - function onLoad() { - ChromeExOAuth.initCallbackPage(); - }; - </script> - </head> - <body onload="onLoad();"> - Redirecting... - </body> -</html> diff --git a/remoting/webapp/me2mom/chrome_ex_oauth.js b/remoting/webapp/me2mom/chrome_ex_oauth.js deleted file mode 100644 index 7e13710..0000000 --- a/remoting/webapp/me2mom/chrome_ex_oauth.js +++ /dev/null @@ -1,593 +0,0 @@ -/** - * Copyright (c) 2010 The Chromium Authors. All rights reserved. Use of this - * source code is governed by a BSD-style license that can be found in the - * LICENSE file. - */ - -/** - * Constructor - no need to invoke directly, call initBackgroundPage instead. - * @constructor - * @param {String} url_request_token The OAuth request token URL. - * @param {String} url_auth_token The OAuth authorize token URL. - * @param {String} url_access_token The OAuth access token URL. - * @param {String} consumer_key The OAuth consumer key. - * @param {String} consumer_secret The OAuth consumer secret. - * @param {String} oauth_scope The OAuth scope parameter. - * @param {Object} opt_args Optional arguments. Recognized parameters: - * "app_name" {String} Name of the current application - * "callback_page" {String} If you renamed chrome_ex_oauth.html, the name - * this file was renamed to. - */ -function ChromeExOAuth(url_request_token, url_auth_token, url_access_token, - consumer_key, consumer_secret, oauth_scope, opt_args) { - this.url_request_token = url_request_token; - this.url_auth_token = url_auth_token; - this.url_access_token = url_access_token; - this.consumer_key = consumer_key; - this.consumer_secret = consumer_secret; - this.oauth_scope = oauth_scope; - this.app_name = opt_args && opt_args['app_name'] || - "ChromeExOAuth Library"; - this.key_token = "oauth_token"; - this.key_token_secret = "oauth_token_secret"; - this.callback_page = opt_args && opt_args['callback_page'] || - "chrome_ex_oauth.html"; - this.auth_params = {}; - if (opt_args && opt_args['auth_params']) { - for (key in opt_args['auth_params']) { - if (opt_args['auth_params'].hasOwnProperty(key)) { - this.auth_params[key] = opt_args['auth_params'][key]; - } - } - } -}; - -/******************************************************************************* - * PUBLIC API METHODS - * Call these from your background page. - ******************************************************************************/ - -/** - * Initializes the OAuth helper from the background page. You must call this - * before attempting to make any OAuth calls. - * @param {Object} oauth_config Configuration parameters in a JavaScript object. - * The following parameters are recognized: - * "request_url" {String} OAuth request token URL. - * "authorize_url" {String} OAuth authorize token URL. - * "access_url" {String} OAuth access token URL. - * "consumer_key" {String} OAuth consumer key. - * "consumer_secret" {String} OAuth consumer secret. - * "scope" {String} OAuth access scope. - * "app_name" {String} Application name. - * "auth_params" {Object} Additional parameters to pass to the - * Authorization token URL. For an example, 'hd', 'hl', 'btmpl': - * http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth - * @return {ChromeExOAuth} An initialized ChromeExOAuth object. - */ -ChromeExOAuth.initBackgroundPage = function(oauth_config) { - window.chromeExOAuthConfig = oauth_config; - window.chromeExOAuth = ChromeExOAuth.fromConfig(oauth_config); - window.chromeExOAuthRedirectStarted = false; - window.chromeExOAuthRequestingAccess = false; - - var url_match = chrome.extension.getURL(window.chromeExOAuth.callback_page); - var tabs = {}; - chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { - if (changeInfo.url && - changeInfo.url.substr(0, url_match.length) === url_match && - changeInfo.url != tabs[tabId] && - window.chromeExOAuthRequestingAccess == false) { - chrome.tabs.create({ 'url' : changeInfo.url }, function(tab) { - tabs[tab.id] = tab.url; - chrome.tabs.remove(tabId); - }); - } - }); - - return window.chromeExOAuth; -}; - -/** - * Authorizes the current user with the configued API. You must call this - * before calling sendSignedRequest. - * @param {Function} callback A function to call once an access token has - * been obtained. This callback will be passed the following arguments: - * token {String} The OAuth access token. - * secret {String} The OAuth access token secret. - */ -ChromeExOAuth.prototype.authorize = function(callback) { - if (this.hasToken()) { - callback(this.getToken(), this.getTokenSecret()); - } else { - window.chromeExOAuthOnAuthorize = function(token, secret) { - callback(token, secret); - }; - chrome.tabs.create({ 'url' :chrome.extension.getURL(this.callback_page) }); - } -}; - -/** - * Clears any OAuth tokens stored for this configuration. Effectively a - * "logout" of the configured OAuth API. - */ -ChromeExOAuth.prototype.clearTokens = function() { - delete localStorage[this.key_token + encodeURI(this.oauth_scope)]; - delete localStorage[this.key_token_secret + encodeURI(this.oauth_scope)]; -}; - -/** - * Returns whether a token is currently stored for this configuration. - * Effectively a check to see whether the current user is "logged in" to - * the configured OAuth API. - * @return {Boolean} True if an access token exists. - */ -ChromeExOAuth.prototype.hasToken = function() { - return !!this.getToken(); -}; - -/** - * Makes an OAuth-signed HTTP request with the currently authorized tokens. - * @param {String} url The URL to send the request to. Querystring parameters - * should be omitted. - * @param {Function} callback A function to be called once the request is - * completed. This callback will be passed the following arguments: - * responseText {String} The text response. - * xhr {XMLHttpRequest} The XMLHttpRequest object which was used to - * send the request. Useful if you need to check response status - * code, etc. - * @param {Object} opt_params Additional parameters to configure the request. - * The following parameters are accepted: - * "method" {String} The HTTP method to use. Defaults to "GET". - * "body" {String} A request body to send. Defaults to null. - * "parameters" {Object} Query parameters to include in the request. - * "headers" {Object} Additional headers to include in the request. - */ -ChromeExOAuth.prototype.sendSignedRequest = function(url, callback, - opt_params) { - var method = opt_params && opt_params['method'] || 'GET'; - var body = opt_params && opt_params['body'] || null; - var params = opt_params && opt_params['parameters'] || {}; - var headers = opt_params && opt_params['headers'] || {}; - - var signedUrl = this.signURL(url, method, params); - - ChromeExOAuth.sendRequest(method, signedUrl, headers, body, function (xhr) { - if (xhr.readyState == 4) { - callback(xhr.responseText, xhr); - } - }); -}; - -/** - * Adds the required OAuth parameters to the given url and returns the - * result. Useful if you need a signed url but don't want to make an XHR - * request. - * @param {String} method The http method to use. - * @param {String} url The base url of the resource you are querying. - * @param {Object} opt_params Query parameters to include in the request. - * @return {String} The base url plus any query params plus any OAuth params. - */ -ChromeExOAuth.prototype.signURL = function(url, method, opt_params) { - var token = this.getToken(); - var secret = this.getTokenSecret(); - if (!token || !secret) { - throw new Error("No oauth token or token secret"); - } - - var params = opt_params || {}; - - var result = OAuthSimple().sign({ - action : method, - path : url, - parameters : params, - signatures: { - consumer_key : this.consumer_key, - shared_secret : this.consumer_secret, - oauth_secret : secret, - oauth_token: token - } - }); - - return result.signed_url; -}; - -/** - * Generates the Authorization header based on the oauth parameters. - * @param {String} url The base url of the resource you are querying. - * @param {Object} opt_params Query parameters to include in the request. - * @return {String} An Authorization header containing the oauth_* params. - */ -ChromeExOAuth.prototype.getAuthorizationHeader = function(url, method, - opt_params) { - var token = this.getToken(); - var secret = this.getTokenSecret(); - if (!token || !secret) { - throw new Error("No oauth token or token secret"); - } - - var params = opt_params || {}; - - return OAuthSimple().getHeaderString({ - action: method, - path : url, - parameters : params, - signatures: { - consumer_key : this.consumer_key, - shared_secret : this.consumer_secret, - oauth_secret : secret, - oauth_token: token - } - }); -}; - -/******************************************************************************* - * PRIVATE API METHODS - * Used by the library. There should be no need to call these methods directly. - ******************************************************************************/ - -/** - * Creates a new ChromeExOAuth object from the supplied configuration object. - * @param {Object} oauth_config Configuration parameters in a JavaScript object. - * The following parameters are recognized: - * "request_url" {String} OAuth request token URL. - * "authorize_url" {String} OAuth authorize token URL. - * "access_url" {String} OAuth access token URL. - * "consumer_key" {String} OAuth consumer key. - * "consumer_secret" {String} OAuth consumer secret. - * "scope" {String} OAuth access scope. - * "app_name" {String} Application name. - * "auth_params" {Object} Additional parameters to pass to the - * Authorization token URL. For an example, 'hd', 'hl', 'btmpl': - * http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth - * @return {ChromeExOAuth} An initialized ChromeExOAuth object. - */ -ChromeExOAuth.fromConfig = function(oauth_config) { - return new ChromeExOAuth( - oauth_config['request_url'], - oauth_config['authorize_url'], - oauth_config['access_url'], - oauth_config['consumer_key'], - oauth_config['consumer_secret'], - oauth_config['scope'], - { - 'app_name' : oauth_config['app_name'], - 'auth_params' : oauth_config['auth_params'] - } - ); -}; - -/** - * Initializes chrome_ex_oauth.html and redirects the page if needed to start - * the OAuth flow. Once an access token is obtained, this function closes - * chrome_ex_oauth.html. - */ -ChromeExOAuth.initCallbackPage = function() { - var background_page = chrome.extension.getBackgroundPage(); - var oauth_config = background_page.chromeExOAuthConfig; - var oauth = ChromeExOAuth.fromConfig(oauth_config); - background_page.chromeExOAuthRedirectStarted = true; - oauth.initOAuthFlow(function (token, secret) { - background_page.chromeExOAuthOnAuthorize(token, secret); - background_page.chromeExOAuthRedirectStarted = false; - chrome.tabs.getSelected(null, function (tab) { - chrome.tabs.remove(tab.id); - }); - }); -}; - -/** - * Sends an HTTP request. Convenience wrapper for XMLHttpRequest calls. - * @param {String} method The HTTP method to use. - * @param {String} url The URL to send the request to. - * @param {Object} headers Optional request headers in key/value format. - * @param {String} body Optional body content. - * @param {Function} callback Function to call when the XMLHttpRequest's - * ready state changes. See documentation for XMLHttpRequest's - * onreadystatechange handler for more information. - */ -ChromeExOAuth.sendRequest = function(method, url, headers, body, callback) { - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function(data) { - callback(xhr, data); - } - xhr.open(method, url, true); - if (headers) { - for (var header in headers) { - if (headers.hasOwnProperty(header)) { - xhr.setRequestHeader(header, headers[header]); - } - } - } - xhr.send(body); -}; - -/** - * Decodes a URL-encoded string into key/value pairs. - * @param {String} encoded An URL-encoded string. - * @return {Object} An object representing the decoded key/value pairs found - * in the encoded string. - */ -ChromeExOAuth.formDecode = function(encoded) { - var params = encoded.split("&"); - var decoded = {}; - for (var i = 0, param; param = params[i]; i++) { - var keyval = param.split("="); - if (keyval.length == 2) { - var key = ChromeExOAuth.fromRfc3986(keyval[0]); - var val = ChromeExOAuth.fromRfc3986(keyval[1]); - decoded[key] = val; - } - } - return decoded; -}; - -/** - * Returns the current window's querystring decoded into key/value pairs. - * @return {Object} A object representing any key/value pairs found in the - * current window's querystring. - */ -ChromeExOAuth.getQueryStringParams = function() { - var urlparts = window.location.href.split("?"); - if (urlparts.length >= 2) { - var querystring = urlparts.slice(1).join("?"); - return ChromeExOAuth.formDecode(querystring); - } - return {}; -}; - -/** - * Binds a function call to a specific object. This function will also take - * a variable number of additional arguments which will be prepended to the - * arguments passed to the bound function when it is called. - * @param {Function} func The function to bind. - * @param {Object} obj The object to bind to the function's "this". - * @return {Function} A closure that will call the bound function. - */ -ChromeExOAuth.bind = function(func, obj) { - var newargs = Array.prototype.slice.call(arguments).slice(2); - return function() { - var combinedargs = newargs.concat(Array.prototype.slice.call(arguments)); - func.apply(obj, combinedargs); - }; -}; - -/** - * Encodes a value according to the RFC3986 specification. - * @param {String} val The string to encode. - */ -ChromeExOAuth.toRfc3986 = function(val){ - return encodeURIComponent(val) - .replace(/\!/g, "%21") - .replace(/\*/g, "%2A") - .replace(/'/g, "%27") - .replace(/\(/g, "%28") - .replace(/\)/g, "%29"); -}; - -/** - * Decodes a string that has been encoded according to RFC3986. - * @param {String} val The string to decode. - */ -ChromeExOAuth.fromRfc3986 = function(val){ - var tmp = val - .replace(/%21/g, "!") - .replace(/%2A/g, "*") - .replace(/%27/g, "'") - .replace(/%28/g, "(") - .replace(/%29/g, ")"); - return decodeURIComponent(tmp); -}; - -/** - * Adds a key/value parameter to the supplied URL. - * @param {String} url An URL which may or may not contain querystring values. - * @param {String} key A key - * @param {String} value A value - * @return {String} The URL with URL-encoded versions of the key and value - * appended, prefixing them with "&" or "?" as needed. - */ -ChromeExOAuth.addURLParam = function(url, key, value) { - var sep = (url.indexOf('?') >= 0) ? "&" : "?"; - return url + sep + - ChromeExOAuth.toRfc3986(key) + "=" + ChromeExOAuth.toRfc3986(value); -}; - -/** - * Stores an OAuth token for the configured scope. - * @param {String} token The token to store. - */ -ChromeExOAuth.prototype.setToken = function(token) { - localStorage[this.key_token + encodeURI(this.oauth_scope)] = token; -}; - -/** - * Retrieves any stored token for the configured scope. - * @return {String} The stored token. - */ -ChromeExOAuth.prototype.getToken = function() { - return localStorage[this.key_token + encodeURI(this.oauth_scope)]; -}; - -/** - * Stores an OAuth token secret for the configured scope. - * @param {String} secret The secret to store. - */ -ChromeExOAuth.prototype.setTokenSecret = function(secret) { - localStorage[this.key_token_secret + encodeURI(this.oauth_scope)] = secret; -}; - -/** - * Retrieves any stored secret for the configured scope. - * @return {String} The stored secret. - */ -ChromeExOAuth.prototype.getTokenSecret = function() { - return localStorage[this.key_token_secret + encodeURI(this.oauth_scope)]; -}; - -/** - * Starts an OAuth authorization flow for the current page. If a token exists, - * no redirect is needed and the supplied callback is called immediately. - * If this method detects that a redirect has finished, it grabs the - * appropriate OAuth parameters from the URL and attempts to retrieve an - * access token. If no token exists and no redirect has happened, then - * an access token is requested and the page is ultimately redirected. - * @param {Function} callback The function to call once the flow has finished. - * This callback will be passed the following arguments: - * token {String} The OAuth access token. - * secret {String} The OAuth access token secret. - */ -ChromeExOAuth.prototype.initOAuthFlow = function(callback) { - if (!this.hasToken()) { - var params = ChromeExOAuth.getQueryStringParams(); - if (params['chromeexoauthcallback'] == 'true') { - var oauth_token = params['oauth_token']; - var oauth_verifier = params['oauth_verifier'] - this.getAccessToken(oauth_token, oauth_verifier, callback); - } else { - var request_params = { - 'url_callback_param' : 'chromeexoauthcallback' - } - this.getRequestToken(function(url) { - window.location.href = url; - }, request_params); - } - } else { - callback(this.getToken(), this.getTokenSecret()); - } -}; - -/** - * Requests an OAuth request token. - * @param {Function} callback Function to call once the authorize URL is - * calculated. This callback will be passed the following arguments: - * url {String} The URL the user must be redirected to in order to - * approve the token. - * @param {Object} opt_args Optional arguments. The following parameters - * are accepted: - * "url_callback" {String} The URL the OAuth provider will redirect to. - * "url_callback_param" {String} A parameter to include in the callback - * URL in order to indicate to this library that a redirect has - * taken place. - */ -ChromeExOAuth.prototype.getRequestToken = function(callback, opt_args) { - if (typeof callback !== "function") { - throw new Error("Specified callback must be a function."); - } - var url = opt_args && opt_args['url_callback'] || - window && window.top && window.top.location && - window.top.location.href; - - var url_param = opt_args && opt_args['url_callback_param'] || - "chromeexoauthcallback"; - var url_callback = ChromeExOAuth.addURLParam(url, url_param, "true"); - - var result = OAuthSimple().sign({ - path : this.url_request_token, - parameters: { - "xoauth_displayname" : this.app_name, - "scope" : this.oauth_scope, - "oauth_callback" : url_callback - }, - signatures: { - consumer_key : this.consumer_key, - shared_secret : this.consumer_secret - } - }); - var onToken = ChromeExOAuth.bind(this.onRequestToken, this, callback); - ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken); -}; - -/** - * Called when a request token has been returned. Stores the request token - * secret for later use and sends the authorization url to the supplied - * callback (for redirecting the user). - * @param {Function} callback Function to call once the authorize URL is - * calculated. This callback will be passed the following arguments: - * url {String} The URL the user must be redirected to in order to - * approve the token. - * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the - * request token. - */ -ChromeExOAuth.prototype.onRequestToken = function(callback, xhr) { - if (xhr.readyState == 4) { - if (xhr.status == 200) { - var params = ChromeExOAuth.formDecode(xhr.responseText); - var token = params['oauth_token']; - this.setTokenSecret(params['oauth_token_secret']); - var url = ChromeExOAuth.addURLParam(this.url_auth_token, - "oauth_token", token); - for (var key in this.auth_params) { - if (this.auth_params.hasOwnProperty(key)) { - url = ChromeExOAuth.addURLParam(url, key, this.auth_params[key]); - } - } - callback(url); - } else { - throw new Error("Fetching request token failed. Status " + xhr.status); - } - } -}; - -/** - * Requests an OAuth access token. - * @param {String} oauth_token The OAuth request token. - * @param {String} oauth_verifier The OAuth token verifier. - * @param {Function} callback The function to call once the token is obtained. - * This callback will be passed the following arguments: - * token {String} The OAuth access token. - * secret {String} The OAuth access token secret. - */ -ChromeExOAuth.prototype.getAccessToken = function(oauth_token, oauth_verifier, - callback) { - if (typeof callback !== "function") { - throw new Error("Specified callback must be a function."); - } - var bg = chrome.extension.getBackgroundPage(); - if (bg.chromeExOAuthRequestingAccess == false) { - bg.chromeExOAuthRequestingAccess = true; - - var result = OAuthSimple().sign({ - path : this.url_access_token, - parameters: { - "oauth_token" : oauth_token, - "oauth_verifier" : oauth_verifier - }, - signatures: { - consumer_key : this.consumer_key, - shared_secret : this.consumer_secret, - oauth_secret : this.getTokenSecret(this.oauth_scope) - } - }); - - var onToken = ChromeExOAuth.bind(this.onAccessToken, this, callback); - ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken); - } -}; - -/** - * Called when an access token has been returned. Stores the access token and - * access token secret for later use and sends them to the supplied callback. - * @param {Function} callback The function to call once the token is obtained. - * This callback will be passed the following arguments: - * token {String} The OAuth access token. - * secret {String} The OAuth access token secret. - * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the - * access token. - */ -ChromeExOAuth.prototype.onAccessToken = function(callback, xhr) { - if (xhr.readyState == 4) { - var bg = chrome.extension.getBackgroundPage(); - if (xhr.status == 200) { - var params = ChromeExOAuth.formDecode(xhr.responseText); - var token = params["oauth_token"]; - var secret = params["oauth_token_secret"]; - this.setToken(token); - this.setTokenSecret(secret); - bg.chromeExOAuthRequestingAccess = false; - callback(token, secret); - } else { - bg.chromeExOAuthRequestingAccess = false; - throw new Error("Fetching access token failed with status " + xhr.status); - } - } -};
\ No newline at end of file diff --git a/remoting/webapp/me2mom/chrome_ex_oauthsimple.js b/remoting/webapp/me2mom/chrome_ex_oauthsimple.js deleted file mode 100644 index af0fe8a..0000000 --- a/remoting/webapp/me2mom/chrome_ex_oauthsimple.js +++ /dev/null @@ -1,458 +0,0 @@ -/* OAuthSimple - * A simpler version of OAuth - * - * author: jr conlin - * mail: src@anticipatr.com - * copyright: unitedHeroes.net - * version: 1.0 - * url: http://unitedHeroes.net/OAuthSimple - * - * Copyright (c) 2009, 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; - -if (OAuthSimple === undefined) -{ - /* 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) - { -/* if (api_key == undefined) - throw("Missing argument: api_key (oauth_consumer_key) for OAuthSimple. This is usually provided by the hosting site."); - if (shared_secret == undefined) - throw("Missing argument: shared_secret (shared secret) for OAuthSimple. This is usually provided by the hosting site."); -*/ this._secrets={}; - this._parameters={}; - - // General configuration options. - if (consumer_key !== undefined) { - this._secrets['consumer_key'] = consumer_key; - } - if (shared_secret !== undefined) { - this._secrets['shared_secret'] = shared_secret; - } - this._default_signature_method= "HMAC-SHA1"; - this._action = "GET"; - this._nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - - - this.reset = function() { - this._parameters={}; - this._path=undefined; - return this; - }; - - /** 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 === undefined) { - parameters = {}; - } - if (typeof(parameters) == 'string') { - parameters=this._parseParameterString(parameters); - } - this._parameters = parameters; - if (this._parameters['oauth_nonce'] === undefined) { - this._getNonce(); - } - if (this._parameters['oauth_timestamp'] === undefined) { - this._getTimestamp(); - } - if (this._parameters['oauth_method'] === undefined) { - this.setSignatureMethod(); - } - if (this._parameters['oauth_consumer_key'] === undefined) { - this._getApiKey(); - } - if(this._parameters['oauth_token'] === undefined) { - 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'); - } - 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 === undefined) { - 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 === undefined) { - throw('Missing required consumer_key in OAuthSimple.setTokensAndSecrets'); - } - if (this._secrets.shared_secret === undefined) { - throw('Missing required shared_secret in OAuthSimple.setTokensAndSecrets'); - } - if ((this._secrets.oauth_token !== undefined) && (this._secrets.oauth_secret === undefined)) { - 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 === undefined) { - method = this._default_signature_method; - } - //TODO: accept things other than PlainText or SHA-MAC1 - if (method.toUpperCase().match(/(PLAINTEXT|HMAC-SHA1)/) === undefined) { - 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 === undefined) { - args = {}; - } - // Set any given parameters - if(args['action'] !== undefined) { - this.setAction(args['action']); - } - if (args['path'] !== undefined) { - this.setPath(args['path']); - } - if (args['method'] !== undefined) { - this.setSignatureMethod(args['method']); - } - this.setTokensAndSecrets(args['signatures']); - if (args['parameters'] !== undefined){ - this.setParameters(args['parameters']); - } - // check the parameters - var normParams = this._normalizedParameters(); - this._parameters['oauth_signature']=this._generateSignature(normParams); - return { - parameters: this._parameters, - 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'] === undefined) { - this.sign(args); - } - - var result = 'OAuth '; - for (var pName in this._parameters) - { - if (!pName.match(/^oauth/)) { - 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 === undefined) { - 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 === undefined) { - 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 === undefined) { - 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'] === undefined) { - return ''; - } - if (this._secrets['oauth_token'] === undefined) { - 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.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._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._generateSignature = function() { - - var secretKey = this._oauthEscape(this._secrets.shared_secret)+'&'+ - this._oauthEscape(this._secrets.oauth_secret); - if (this._parameters['oauth_signature_method'] == 'PLAINTEXT') - { - return secretKey; - } - if (this._parameters['oauth_signature_method'] == 'HMAC-SHA1') - { - var sigString = this._oauthEscape(this._action)+'&'+this._oauthEscape(this._path)+'&'+this._oauthEscape(this._normalizedParameters()); - return this.b64_hmac_sha1(secretKey,sigString); - } - return null; - }; - - return this; - }; -} diff --git a/remoting/webapp/me2mom/manifest.json b/remoting/webapp/me2mom/manifest.json index 110f8dc..7b469ce4 100644 --- a/remoting/webapp/me2mom/manifest.json +++ b/remoting/webapp/me2mom/manifest.json @@ -13,6 +13,7 @@ }, "permissions": [ "tabs", + "https://accounts.google.com/o/oauth2/*", "https://www.google.com/accounts/*", "https://www.googleapis.com/chromoting/*" ], diff --git a/remoting/webapp/me2mom/oauth2.js b/remoting/webapp/me2mom/oauth2.js new file mode 100644 index 0000000..9b34129 --- /dev/null +++ b/remoting/webapp/me2mom/oauth2.js @@ -0,0 +1,138 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Declare an OAuth2 class to handle retrieval/storage of an OAuth2 token. +// +// Ideally, this should implement the OAuth2 PostMessage flow to avoid needing +// to copy and paste a code, but that does not support extension URL schemes +// quite yet. Instead, we currently use the native app flow with an +// authorization code that the user must cut/paste. +function OAuth2() { + this.OAUTH2_REFRESH_TOKEN_NAME = 'oauth2_refresh_token'; + + this.client_id = encodeURIComponent( + '440925447803-m890isgsr23kdkcu2erd4mirnrjalf98.' + + 'apps.googleusercontent.com'); + this.client_secret = encodeURIComponent('TgKrL73H2kJe6Ir0ufp7bf6e'); + this.scope = encodeURIComponent( + 'https://www.googleapis.com/auth/chromoting ' + + 'https://www.googleapis.com/auth/googletalk'); + this.redirect_uri = encodeURIComponent('urn:ietf:wg:oauth:2.0:oob'); +} + +OAuth2.prototype.isAuthenticated = function() { + if(this.getRefreshToken()) { + return true; + } + return false; +} + +OAuth2.prototype.clear = function() { + remoting.removeItem(this.OAUTH2_REFRESH_TOKEN_NAME); + delete this.access_token; + delete this.access_token_expiration; +} + +OAuth2.prototype.setRefreshToken = function(token) { + remoting.setItem(this.OAUTH2_REFRESH_TOKEN_NAME, token); +} + +OAuth2.prototype.getRefreshToken = function(token) { + return remoting.getItem(this.OAUTH2_REFRESH_TOKEN_NAME); +} + +OAuth2.prototype.setAccessToken = function(token, expiration) { + this.access_token = token; + // Offset by 30 seconds to account for RTT issues. + // TODO(ajwong): See if this is necessary, or of the protocol already + // accounts for RTT. + this.access_token_expiration = expiration - 30000; +} + +OAuth2.prototype.needsNewAccessToken = function() { + if (!this.isAuthenticated()) { + throw "Not Authenticated."; + } + if (!this.access_token) { + return true; + } + if (Date.now() > this.access_token_expiration) { + return true; + } + return false; +} + +OAuth2.prototype.getAccessToken = function() { + if (this.needsNewAccessToken()) { + throw "Access Token expired."; + } + return this.access_token; +} + +OAuth2.prototype.refreshAccessToken = function(on_done) { + if (!this.isAuthenticated()) { + throw "Not Authenticated."; + } + var xhr = new XMLHttpRequest(); + var that = this; + xhr.onreadystatechange = function() { + if (xhr.readyState != 4) { + return; + } + if (xhr.status == 200) { + tokens = JSON.parse(xhr.responseText); + that.setAccessToken(tokens['access_token'], + tokens['expires_in'] * 1000 + Date.now()); + } else { + console.log("Refresh access token failed. Status: " + xhr.status + + " response: " + xhr.responseText); + } + on_done(); + }; + xhr.open('POST', 'https://accounts.google.com/o/oauth2/token', true); + xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + var post_data = 'client_id=' + this.client_id + + '&client_secret=' + this.client_secret + + '&refresh_token=' + encodeURIComponent(this.getRefreshToken()) + + '&grant_type=refresh_token'; + xhr.send(post_data); +} + +OAuth2.prototype.openOAuth2Window = function() { + var GET_CODE_URL = 'https://accounts.google.com/o/oauth2/auth?' + + 'client_id=' + this.client_id + + '&redirect_uri=' + this.redirect_uri + + '&scope=' + this.scope + + '&response_type=code'; + window.open(GET_CODE_URL); +} + +OAuth2.prototype.exchangeCodeForToken = function(code, on_done) { + var xhr = new XMLHttpRequest(); + var that = this; + xhr.onreadystatechange = function() { + if (xhr.readyState != 4) { + return; + } + if (xhr.status == 200) { + tokens = JSON.parse(xhr.responseText); + that.setRefreshToken(tokens['refresh_token']); + that.setAccessToken(tokens['access_token'], + tokens['expires_in'] + Date.now()); + } else { + console.log("Code exchnage failed. Status: " + xhr.status + + " response: " + xhr.responseText); + } + on_done(); + }; + xhr.open('POST', 'https://accounts.google.com/o/oauth2/token', true); + xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + var post_data = 'client_id=' + this.client_id + + '&client_secret=' + this.client_secret + + '&redirect_uri=' + this.redirect_uri + + '&code=' + encodeURIComponent(code) + + '&grant_type=authorization_code'; + xhr.send(post_data); +} + diff --git a/remoting/webapp/me2mom/remoting.js b/remoting/webapp/me2mom/remoting.js index 2f364c11..656aa15 100644 --- a/remoting/webapp/me2mom/remoting.js +++ b/remoting/webapp/me2mom/remoting.js @@ -2,20 +2,28 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(ajwong): This seems like a bad idea to share the exact same object +// with the background page. Why are we doing it like this? var remoting = chrome.extension.getBackgroundPage().remoting; + XMPP_LOGIN_NAME = 'xmpp_login'; XMPP_TOKEN_NAME = 'xmpp_token'; -OAUTH2_TOKEN_NAME = 'oauth2_token'; HOST_PLUGIN_ID = 'host_plugin_id'; function updateAuthStatus_() { - var oauth1_status = document.getElementById('oauth1_status'); - if (remoting.oauth.hasToken()) { - oauth1_status.innerText = 'OK'; - oauth1_status.style.color = 'green'; + var oauth2_status = document.getElementById('oauth2_status'); + if (remoting.oauth2.isAuthenticated()) { + oauth2_status.innerText = 'OK'; + oauth2_status.style.color = 'green'; + document.getElementById('oauth2_code_button').style.display = 'none'; + document.getElementById('oauth2_clear_button').style.display = 'inline'; + document.getElementById('oauth2_form').style.display = 'none'; } else { - oauth1_status.innerText = 'Unauthorized'; - oauth1_status.style.color = 'red'; + oauth2_status.innerText = 'Unauthorized'; + oauth2_status.style.color = 'red'; + document.getElementById('oauth2_code_button').style.display = 'inline'; + document.getElementById('oauth2_clear_button').style.display = 'none'; + document.getElementById('oauth2_form').style.display = 'inline'; } var xmpp_status = document.getElementById('xmpp_status'); if (remoting.getItem(XMPP_TOKEN_NAME) && remoting.getItem(XMPP_LOGIN_NAME)) { @@ -78,7 +86,7 @@ function initBackgroundFuncs_() { remoting.getItem = chrome.extension.getBackgroundPage().getItem; remoting.setItem = chrome.extension.getBackgroundPage().setItem; remoting.removeItem = chrome.extension.getBackgroundPage().removeItem; - remoting.oauth = chrome.extension.getBackgroundPage().oauth; + remoting.oauth2 = new OAuth2(); } function authorizeXmpp(form) { @@ -131,12 +139,12 @@ function authorizeXmpp(form) { xhr.send(post_data); } -function authorizeOAuth1() { - remoting.oauth.authorize(updateAuthStatus_); +function authorizeOAuth2(code) { + remoting.oauth2.exchangeCodeForToken(code, updateAuthStatus_); } -function clearOAuth1() { - remoting.oauth.clearTokens(); +function clearOAuth2() { + remoting.oauth2.clear(); updateAuthStatus_(); } @@ -243,7 +251,7 @@ function showConnectError_(responseCode, responseString) { setClientMode('connect_failed'); } -function parseServerResponse_(reply, xhr) { +function parseServerResponse_(xhr) { if (xhr.status == 200) { var host = JSON.parse(xhr.responseText); if (host.data && host.data.jabberId) { @@ -255,6 +263,24 @@ function parseServerResponse_(reply, xhr) { showConnectError_(xhr.status, xhr.responseText); } +function resolveSupportId(support_id) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState != 4) { + return; + } + parseServerResponse_(xhr); + }; + + xhr.open('GET', + 'https://www.googleapis.com/chromoting/v1/support-hosts/' + + encodeURIComponent(support_id), + true); + xhr.setRequestHeader('Authorization', + 'OAuth ' + remoting.oauth2.getAccessToken()); + xhr.send(null); +} + function tryConnect(form) { remoting.accessCode = form['access_code_entry'].value; // TODO(jamiewalch): Since the mapping from (SupportId, HostSecret) to @@ -264,10 +290,14 @@ function tryConnect(form) { showConnectError_(404); } else { setClientMode('connecting'); - var urlBase = 'https://www.googleapis.com/chromoting/v1/support-hosts/'; - remoting.oauth.sendSignedRequest( - urlBase + '' + encodeURIComponent(parts[0]) + '', - parseServerResponse_); + if (remoting.oauth2.needsNewAccessToken()) { + remoting.oauth2.refreshAccessToken(function() { + resolveSupportId(parts[0]); + }); + return; + } else { + resolveSupportId(parts[0]); + } } } |