diff options
author | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-21 00:21:15 +0000 |
---|---|---|
committer | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-21 00:21:15 +0000 |
commit | 285a00882abba22fee14b904db1b48a7c21371af (patch) | |
tree | 386175baba80fab8102a316f5214cc46fdb3a02a | |
parent | c2f42890d6214938a612da4f0c096d8e970cad99 (diff) | |
download | chromium_src-285a00882abba22fee14b904db1b48a7c21371af.zip chromium_src-285a00882abba22fee14b904db1b48a7c21371af.tar.gz chromium_src-285a00882abba22fee14b904db1b48a7c21371af.tar.bz2 |
Update the appengine code to use OAuth2 and break the gdata dependency.
BUG=none
TEST=Connect still works.
Review URL: http://codereview.chromium.org/7033042
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@86183 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | remoting/client/appengine/api.py | 33 | ||||
-rw-r--r-- | remoting/client/appengine/app.yaml | 2 | ||||
-rw-r--r-- | remoting/client/appengine/auth.py | 337 | ||||
-rw-r--r-- | remoting/client/appengine/chromoting_oauth_setup.html | 27 | ||||
-rw-r--r-- | remoting/client/appengine/chromoting_session.html | 2 | ||||
-rw-r--r-- | remoting/client/appengine/hostlist.html | 18 | ||||
-rw-r--r-- | remoting/client/appengine/main.py | 7 | ||||
-rw-r--r-- | remoting/client/appengine/static_files/client.js | 45 | ||||
-rw-r--r-- | remoting/client/extension/manifest.json | 15 |
9 files changed, 206 insertions, 280 deletions
diff --git a/remoting/client/appengine/api.py b/remoting/client/appengine/api.py index ec16fc6..e1e2ddb 100644 --- a/remoting/client/appengine/api.py +++ b/remoting/client/appengine/api.py @@ -10,8 +10,7 @@ import logging from django.utils import simplejson as json -import gdata.client - +from google.appengine.api import urlfetch from google.appengine.ext import webapp from google.appengine.ext.webapp import util from google.appengine.ext.webapp.util import login_required @@ -30,26 +29,26 @@ class GetXmppTokenHandler(webapp.RequestHandler): self.response.out.write('User has not authenticated') self.set_status(400) + class GetHostListHandler(webapp.RequestHandler): """Proxies the host-list handlers on the Chromoting directory.""" @login_required def get(self): - try: - client = gdata.client.GDClient() - host_list_json = client.Request( - method='GET', - uri="https://www.googleapis.com/chromoting/v1/@me/hosts", - converter=None, - desired_class=None, - auth_token=auth.GetChromotingToken()) + if not auth.HasOAuth2Tokens(): + self.response.set_status(403) self.response.headers['Content-Type'] = 'application/json' - self.response.out.write(host_list_json.read()) - except auth.NotAuthenticated: - self.response.out.write('User has not authenticated') - self.response.set_status(400) - except (gdata.client.Unauthorized, gdata.client.RequestError), inst: - self.response.out.write(inst.reason) - self.response.set_status(inst.status) + self.response.out.write( + '{"error": { "code": -1, "message": "No OAuth2 token" }}') + return + result = urlfetch.fetch( + url = 'https://www.googleapis.com/chromoting/v1/@me/hosts', + method = urlfetch.GET, + headers = {'Authorization': 'OAuth ' + auth.GetAccessToken()}) + self.response.set_status(result.status_code) + for i in result.headers: + self.response.headers[i] = result.headers[i] + self.response.out.write(result.content) + def main(): application = webapp.WSGIApplication( diff --git a/remoting/client/appengine/app.yaml b/remoting/client/appengine/app.yaml index ff9db53..a5d7436 100644 --- a/remoting/client/appengine/app.yaml +++ b/remoting/client/appengine/app.yaml @@ -1,5 +1,5 @@ application: google.com:chromoting -version: 1 +version: 2 runtime: python api_version: 1 diff --git a/remoting/client/appengine/auth.py b/remoting/client/appengine/auth.py index 40433cc..54be383 100644 --- a/remoting/client/appengine/auth.py +++ b/remoting/client/appengine/auth.py @@ -12,59 +12,85 @@ initiating authentication flows, and for managing credential storage per user. """ import os +import re +import time +import urllib +from urlparse import urlparse -import gdata.gauth -import gdata.client from google.appengine.ext import db +from google.appengine.api import urlfetch from google.appengine.api import users from google.appengine.ext import webapp from google.appengine.ext.webapp import template from google.appengine.ext.webapp import util from google.appengine.ext.webapp.util import login_required +from django.utils import simplejson as json + SCOPES = ['https://www.googleapis.com/auth/chromoting', 'https://www.googleapis.com/auth/googletalk' ] +# Development OAuth2 ID and keys. +CLIENT_ID = ('440925447803-d9u05st5jjm3gbe865l0jeaujqfrufrn.' + 'apps.googleusercontent.com') +CLIENT_SECRET = 'Nl4vSQEgDpPMP-1rDEsgs3V7' + class NotAuthenticated(Exception): """API requiring authentication is called with credentials.""" pass -class OAuthInvalidSetup(Exception): - """OAuth configuration on app is not complete.""" - pass +class XmppToken(db.Model): + auth_token = db.StringProperty() -class OAuthConfig(db.Model): - """Stores the configuration data for OAuth. +class OAuth2Tokens(db.Model): + """Stores the Refresh and Access token information for OAuth2.""" + refresh_token = db.StringProperty() + access_token = db.StringProperty() + access_token_expiration = db.IntegerProperty() - Currently used to store the consumer key and secret so that it does not need - to be checked into the source tree. - """ - consumer_key = db.StringProperty() - consumer_secret = db.StringProperty() - httpxmppproxy = db.StringProperty() +def HasOAuth2Tokens(throws=True): + oauth2_tokens = OAuth2Tokens.get_or_insert(GetUserId()) + if oauth2_tokens.refresh_token: + return True; + return False; -def GetChromotingToken(throws=True): - """Retrieves the Chromoting OAuth token for the user. - Args: - throws: bool (optional) Default is True. Throws if no token. +def GetAccessToken(throws=True): + oauth2_tokens = OAuth2Tokens.get_or_insert(GetUserId()) - Returns: - An gdata.gauth.OAuthHmacToken for the current user. - """ - user = users.get_current_user() - access_token = None - if user: - access_token = LoadToken('chromoting_token') - if throws and not access_token: + if not oauth2_tokens.refresh_token: raise NotAuthenticated() - return access_token + + if time.time() > oauth2_tokens.access_token_expiration: + form_fields = { + 'client_id' : CLIENT_ID, + 'client_secret' : CLIENT_SECRET, + 'refresh_token' : oauth2_tokens.refresh_token, + 'grant_type' : 'refresh_token' + } + form_data = urllib.urlencode(form_fields) + result = urlfetch.fetch( + url = 'https://accounts.google.com/o/oauth2/token', + payload = form_data, + method = urlfetch.POST, + headers = {'Content-Type': 'application/x-www-form-urlencoded'}) + if result.status_code != 200: + raise 'something went wrong %d, %s <br />' % ( + result.status_code, result.content) + oauth_json = json.loads(result.content) + oauth2_tokens.access_token = oauth_json['access_token'] + # Give us 30 second buffer to hackily account for RTT on network request. + oauth2_tokens.access_token_expiration = ( + int(oauth_json['expires_in'] + time.time() - 30)) + oauth2_tokens.put() + + return oauth2_tokens.access_token def GetXmppToken(throws=True): @@ -74,26 +100,22 @@ def GetXmppToken(throws=True): throws: bool (optional) Default is True. Throws if no token. Returns: - An gdata.gauth.ClientLoginToken for the current user. + The auth token for the current user. """ - user = users.get_current_user() - access_token = None - if user: - access_token = LoadToken('xmpp_token') - if throws and not access_token: + xmpp_token = XmppToken.get_or_insert(GetUserId()) + if throws and not xmpp_token.auth_token: raise NotAuthenticated() - return access_token + return xmpp_token.auth_token -def ClearChromotingToken(): - """Clears all Chromoting OAuth token state from the datastore.""" - DeleteToken('request_token') - DeleteToken('chromoting_token') +def ClearXmppToken(): + """Clears all Chromoting ClientLogin token state from the datastore.""" + db.delete(db.Key.from_path('XmppToken', GetUserId())) -def ClearXmppToken(): +def ClearOAuth2Token(): """Clears all Chromoting ClientLogin token state from the datastore.""" - DeleteToken('xmpp_token') + db.delete(db.Key.from_path('OAuth2Tokens', GetUserId())) def GetUserId(): @@ -115,116 +137,6 @@ def GetUserId(): return user.user_id() -def LoadToken(name): - """Leads a gdata auth token for the current user. - - Tokens are scoped to each user, and retrieved by a name. - - Args: - name: A string with the name of the token for the current user. - - Returns: - The token associated with the name for the user. - """ - user_id = GetUserId(); - return gdata.gauth.AeLoad(user_id + name) - - -def SaveToken(name, token): - """Saves a gdata auth token for the current user. - - Tokens are scoped to each user, and stored by a name. - - Args: - name: A string with the name of the token. - """ - user_id = GetUserId(); - gdata.gauth.AeSave(token, user_id + name) - - -def DeleteToken(name): - """Deletes a stored gdata auth token for the current user. - - Tokens are scoped to each user, and stored by a name. - - Args: - name: A string with the name of the token. - """ - user_id = GetUserId(); - gdata.gauth.AeDelete(user_id + name) - - -def OAuthConfigKey(): - """Generates a standard key path for this app's OAuth configuration.""" - return db.Key.from_path('OAuthConfig', 'oauth_config') - - -def GetOAuthConfig(throws=True): - """Retrieves the OAuthConfig for this app. - - Returns: - The OAuthConfig object for this app. - - Raises: - OAuthInvalidSetup if no OAuthConfig exists. - """ - config = db.get(OAuthConfigKey()) - if throws and not config: - raise OAuthInvalidSetup() - return config - - -class ChromotingAuthHandler(webapp.RequestHandler): - """Initiates getting the OAuth access token for the user. - - This webapp uses 3-legged OAuth. This handlers performs the first step - of getting the OAuth request token, and then forwarding on to the - Google Accounts authorization endpoint for the second step. The final - step is completed by the ChromotingAuthReturnHandler below. - - FYI, all three steps are collectively known as the "OAuth dance." - """ - @login_required - def get(self): - ClearChromotingToken() - client = gdata.client.GDClient() - - oauth_callback_url = ('http://%s/auth/chromoting_auth_return' % - self.request.host) - request_token = client.GetOAuthToken( - SCOPES, oauth_callback_url, GetOAuthConfig().consumer_key, - consumer_secret=GetOAuthConfig().consumer_secret) - - SaveToken('request_token', request_token) - domain = None # Not on an Google Apps domain. - auth_uri = request_token.generate_authorization_url() - self.redirect(str(auth_uri)) - - -class ChromotingAuthReturnHandler(webapp.RequestHandler): - """Finishes the authorization started in ChromotingAuthHandler.i - - After the user authorizes the OAuth request token at the OAuth request - URL they were redirected to in ChromotingAuthHandler, OAuth will send - them back here with an auth token in the URL. - - This handler retrievies the access token, and stores it completing the - OAuth dance. - """ - @login_required - def get(self): - saved_request_token = LoadToken('request_token') - DeleteToken('request_token') - request_token = gdata.gauth.AuthorizeRequestToken( - saved_request_token, self.request.uri) - - # Upgrade the token and save in the user's datastore - client = gdata.client.GDClient() - access_token = client.GetAccessToken(request_token) - SaveToken('chromoting_token', access_token) - self.redirect("/") - - class XmppAuthHandler(webapp.RequestHandler): """Prompts Google Accounts credentials and retrieves a ClientLogin token. @@ -244,87 +156,102 @@ class XmppAuthHandler(webapp.RequestHandler): self.response.out.write(template.render(path, {})) def post(self): - client = gdata.client.GDClient() email = self.request.get('username') password = self.request.get('password') - try: - client.ClientLogin( - email, password, 'chromoclient', 'chromiumsync') - SaveToken('xmpp_token', client.auth_token) - except gdata.client.CaptchaChallenge: - self.response.out.write('You need to solve a Captcha. ' - 'Unforutnately, we still have to implement that.') + form_fields = { + 'accountType' : 'HOSTED_OR_GOOGLE', + 'Email' : self.request.get('username'), + 'Passwd' : self.request.get('password'), + 'service' : 'chromiumsync', + 'source' : 'chromoplex' + } + form_data = urllib.urlencode(form_fields) + result = urlfetch.fetch( + url = 'https://www.google.com/accounts/ClientLogin', + payload = form_data, + method = urlfetch.POST, + headers = {'Content-Type': 'application/x-www-form-urlencoded'}) + if result.status_code != 200: + self.response.out.write(result.content) + for i in result.headers: + self.response.headers[i] = result.headers[i] + self.response.set_status(result.status_code) + return + + xmpp_token = XmppToken(key_name = GetUserId()) + xmpp_token.auth_token = re.search("Auth=(.*)", result.content).group(1) + xmpp_token.put() self.redirect('/') -class ClearChromotingTokenHandler(webapp.RequestHandler): - """Endpoint for dropping the user's Chromoting token.""" +class ClearXmppTokenHandler(webapp.RequestHandler): + """Endpoint for dropping the user's Xmpp token.""" @login_required def get(self): - ClearChromotingToken() + ClearXmppToken() self.redirect('/') -class ClearXmppTokenHandler(webapp.RequestHandler): - """Endpoint for dropping the user's Xmpp token.""" +class ClearOAuth2TokenHandler(webapp.RequestHandler): + """Endpoint for dropping the user's OAuth2 token.""" @login_required def get(self): - ClearXmppToken() + ClearOAuth2Token() self.redirect('/') -class SetupOAuthHandler(webapp.RequestHandler): - """Administrative page for specifying the OAuth consumer key/secret.""" +class OAuth2ReturnHandler(webapp.RequestHandler): + """Handles the redirect in the OAuth dance.""" @login_required def get(self): - path = os.path.join(os.path.dirname(__file__), - 'chromoting_oauth_setup.html') - self.response.out.write(template.render(path, {})) - - def post(self): - old_consumer_secret = self.request.get('old_consumer_secret') - - query = OAuthConfig.all() - - # If there is an existing key, only allow updating if you know the old - # key. This is a simple safeguard against random users hitting this page. - config = GetOAuthConfig(throws=False) - if config: - if config.consumer_secret != old_consumer_secret: - self.response.set_status(400) - self.response.out.write('Incorrect old consumer secret') - return + code = self.request.get('code') + state = self.request.get('state') + parsed_url = urlparse(self.request.url) + server = parsed_url.scheme + '://' + parsed_url.netloc + form_fields = { + 'client_id' : CLIENT_ID, + 'client_secret' : CLIENT_SECRET, + 'redirect_uri' : server + '/auth/oauth2_return', + 'code' : code, + 'grant_type' : 'authorization_code' + } + form_data = urllib.urlencode(form_fields) + result = urlfetch.fetch( + url = 'https://accounts.google.com/o/oauth2/token', + payload = form_data, + method = urlfetch.POST, + headers = {'Content-Type': 'application/x-www-form-urlencoded'}) + + if result.status_code != 200: + self.response.out.write('something went wrong %d, %s <br />' % + (result.status_code, result.content)) + self.response.out.write( + 'We tried posting %s code(%s) [%s]' % (form_data, code, form_fields)) + self.response.set_status(400) + return + + oauth_json = json.loads(result.content) + oauth2_tokens = OAuth2Tokens(key_name = GetUserId()) + oauth2_tokens.refresh_token = oauth_json['refresh_token'] + oauth2_tokens.access_token = oauth_json['access_token'] + # Give us 30 second buffer to hackily account for RTT on network request. + oauth2_tokens.access_token_expiration = ( + int(oauth_json['expires_in'] + time.time() - 30)) + oauth2_tokens.put() + + if state: + self.redirect(state) else: - config = OAuthConfig(key_name = OAuthConfigKey().id_or_name()) - - # TODO(ajwong): THIS IS A TOTAL HACK! FIX WITH OWN PAGE. - # Currently, this form has one submit button, and 3 pieces of input: - # consumer_key, oauth_secret, and the httpxmppproxy address. The - # HTTP/XMPP proxy should really have its own configuration page. - httpxmppproxy = self.request.get('httpxmppproxy') - if httpxmppproxy: - config.httpxmppproxy = httpxmppproxy - - config.consumer_key = self.request.get('consumer_key') - config.consumer_secret = self.request.get('new_consumer_secret') - config.put() - self.redirect('/') + self.redirect('/') -def GetHttpXmppProxy(): - config = GetOAuthConfig(throws=True) - if not config.httpxmppproxy: - raise OAuthInvalidSetup() - return config.httpxmppproxy def main(): application = webapp.WSGIApplication( [ - ('/auth/chromoting_auth', ChromotingAuthHandler), ('/auth/xmpp_auth', XmppAuthHandler), - ('/auth/chromoting_auth_return', ChromotingAuthReturnHandler), ('/auth/clear_xmpp_token', ClearXmppTokenHandler), - ('/auth/clear_chromoting_token', ClearChromotingTokenHandler), - ('/auth/setup_oauth', SetupOAuthHandler) + ('/auth/clear_oauth2_token', ClearOAuth2TokenHandler), + ('/auth/oauth2_return', OAuth2ReturnHandler) ], debug=True) util.run_wsgi_app(application) diff --git a/remoting/client/appengine/chromoting_oauth_setup.html b/remoting/client/appengine/chromoting_oauth_setup.html deleted file mode 100644 index 1b1537c..0000000 --- a/remoting/client/appengine/chromoting_oauth_setup.html +++ /dev/null @@ -1,27 +0,0 @@ -<!doctype html> -<!-- -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. ---> - -<html> - <head> - <title>Chromoting OAuth setup page.</title> - </head> - <body> - <p>This is an administration page to setup the consumer key and secret - to be used with chromoting. Please don't play with this unless you are - on the Chromoting dev team. - <p> - Move the HttpXmppProxy setup out of this page, and make the semantics less - hacky! - <form method="post" name="auth"> - Consumer Key: <input type="text" name="consumer_key" /> - New Consumer Secret: <input type="password" name="new_consumer_secret" /> - Old Consumer Secret: <input type="password" name="old_consumer_secret" /> - HttpXmppProxy Address: <input type="text" name="httpxmppproxy" /> - <input type="submit" value="Submit" /> - </form> - </body> -</html> diff --git a/remoting/client/appengine/chromoting_session.html b/remoting/client/appengine/chromoting_session.html index 8af2190..6197d6a 100644 --- a/remoting/client/appengine/chromoting_session.html +++ b/remoting/client/appengine/chromoting_session.html @@ -13,7 +13,7 @@ found in the LICENSE file. <!-- // TODO(ajwong): Total Hack. We should be able to read the URL parameters // from JS, and also avoid passing in the connection tokens here. - document.xmppAuthToken="{{xmpp_token.token_string}}"; + document.xmppAuthToken="{{xmpp_token}}"; document.httpXmppProxy="{{http_xmpp_proxy}}"; document.username="{{username}}"; document.hostname="{{hostname}}"; diff --git a/remoting/client/appengine/hostlist.html b/remoting/client/appengine/hostlist.html index 7848db0..10138fe 100644 --- a/remoting/client/appengine/hostlist.html +++ b/remoting/client/appengine/hostlist.html @@ -24,6 +24,12 @@ found in the LICENSE file. <div id="mainview-content"> <div class="page"> <section> + <h3>Http Xmpp Proxy</h3> + <input type="text" id="http_xmpp_proxy" + value="https://chromoting-httpxmpp-dev.corp.google.com" + size="50" /> + </section> + <section> <h3>Host List</h3> <div class="hostlist"> <list id="hostlist-div"> @@ -42,21 +48,21 @@ found in the LICENSE file. </div> </section> <section> - <h3>Chromoting Token</h3> + <h3>OAuth2 Token</h3> <div> -{% ifnotequal chromoting_token None %} +{% if has_oauth2_tokens %} <div>Token Authenticated</div> <button id="subitem" - onclick="window.location='/auth/clear_chromoting_token'"> - Clear Token + onclick="window.location='/auth/clear_oauth2_token'"> + Clear Token </button> {% else %} <div class="error-msg">Token Not Authenticated</div> <button id="subitem" - onclick="window.location='/auth/chromoting_auth'"> + onclick="authorizeOAuth2();"> Authenticate Token </button> -{% endifnotequal %} +{% endif %} </div> </section> <section> diff --git a/remoting/client/appengine/main.py b/remoting/client/appengine/main.py index 36f81d7..878e71e 100644 --- a/remoting/client/appengine/main.py +++ b/remoting/client/appengine/main.py @@ -9,9 +9,6 @@ import os from django.utils import simplejson as json -import gdata.gauth -import gdata.client - from google.appengine.api import users from google.appengine.ext import webapp from google.appengine.ext.webapp import template @@ -25,7 +22,7 @@ class HostListHandler(webapp.RequestHandler): @login_required def get(self): template_params = { - 'chromoting_token': auth.GetChromotingToken(throws=False), + 'has_oauth2_tokens': auth.HasOAuth2Tokens(), 'xmpp_token': auth.GetXmppToken(throws=False) } path = os.path.join(os.path.dirname(__file__), 'hostlist.html') @@ -43,7 +40,7 @@ class ChromotingSessionHandler(webapp.RequestHandler): 'connect_method': self.request.get('connect_method'), 'insecure': self.request.get('insecure'), 'xmpp_token': auth.GetXmppToken(), - 'http_xmpp_proxy': auth.GetHttpXmppProxy(), + 'http_xmpp_proxy': self.request.get('http_xmpp_proxy') } path = os.path.join(os.path.dirname(__file__), 'chromoting_session.html') self.response.out.write(template.render(path, template_params)) diff --git a/remoting/client/appengine/static_files/client.js b/remoting/client/appengine/static_files/client.js index b485467..e2ed6f8 100644 --- a/remoting/client/appengine/static_files/client.js +++ b/remoting/client/appengine/static_files/client.js @@ -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. @@ -190,8 +190,9 @@ function addHostInfo(host) { connect.setAttribute('type', 'button'); connect.setAttribute('value', 'Connect'); connect.setAttribute('onclick', "window.open('session?hostname=" + - encodeURIComponent(host.hostName) + "&hostjid=" + - encodeURIComponent(host.jabberId) + "');"); + encodeURIComponent(host.hostName) + + "&hostjid=" + encodeURIComponent(host.jabberId) + + "');"); span.appendChild(connect); var connectSandboxed = document.createElement('input'); @@ -200,6 +201,8 @@ function addHostInfo(host) { connectSandboxed.setAttribute('onclick', "window.open('session?hostname=" + encodeURIComponent(host.hostName) + "&hostjid=" + encodeURIComponent(host.jabberId) + + "&http_xmpp_proxy=" + encodeURIComponent( + document.getElementById('http_xmpp_proxy').value) + "&connect_method=sandboxed');"); span.appendChild(connectSandboxed); @@ -227,3 +230,39 @@ function addHostInfo(host) { return hostEntry; } + +function updateAuthStatus_() { + var oauth2_status = document.getElementById('oauth2_status'); + if (chromoting.oauth2.isAuthenticated()) { + oauth2_status.innerText = 'Tokens Good'; + oauth2_status.style.color = 'green'; + document.getElementById('oauth2_authorize_button').style.display = 'none'; + document.getElementById('oauth2_clear_button').style.display = 'inline'; + populateHostList(); + } else { + oauth2_status.innerText = 'No Tokens'; + oauth2_status.style.color = 'red'; + document.getElementById('oauth2_authorize_button').style.display = 'inline'; + document.getElementById('oauth2_clear_button').style.display = 'none'; + } +} + +function clearOAuth2() { + chromoting.oauth2.clear(); + updateAuthStatus_(); +} + +function authorizeOAuth2() { + var oauth_code_url = 'https://accounts.google.com/o/oauth2/auth?' + + 'client_id=' + encodeURIComponent( + '440925447803-d9u05st5jjm3gbe865l0jeaujqfrufrn.' + + 'apps.googleusercontent.com') + + '&redirect_uri=' + window.location.origin + '/auth/oauth2_return' + + '&scope=' + encodeURIComponent( + 'https://www.googleapis.com/auth/chromoting ' + + 'https://www.googleapis.com/auth/googletalk') + + '&state=' + encodeURIComponent(window.location.href) + + '&response_type=code'; + window.location.replace(oauth_code_url); +} + diff --git a/remoting/client/extension/manifest.json b/remoting/client/extension/manifest.json deleted file mode 100644 index 05d3da2..0000000 --- a/remoting/client/extension/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "Chromoting Client", - "version": "1.1", - "description": "Lists the Chromoting hosts that the current user can access.", - "browser_action": { - "default_icon": "chromoticon.png", - "default_title": "Chromoting" - }, - "background_page": "background.html", - "permissions": [ - "tabs", - "https://www.googleapis.com/chromoting/", - "https://www.google.com/accounts/" - ] -} |