oauth2.js revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @fileoverview
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * OAuth2 class that handles retrieval/storage of an OAuth2 token.
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Uses a content script to trampoline the OAuth redirect page back into the
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * extension context.  This works around the lack of native support for
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * chrome-extensions in OAuth2.
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// TODO(jamiewalch): Delete this code once Chromoting is a v2 app and uses the
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// identity API (http://crbug.com/ 134213).
162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)'use strict';
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @suppress {duplicate} */
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)var remoting = remoting || {};
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @type {remoting.OAuth2} */
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.oauth2 = null;
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @constructor */
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2 = function() {
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Constants representing keys used for storing persistent state.
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @private */
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_ = 'oauth2-refresh-token';
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @private */
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_REVOKABLE_ =
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'oauth2-refresh-token-revokable';
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @private */
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.KEY_ACCESS_TOKEN_ = 'oauth2-access-token';
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @private */
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.KEY_XSRF_TOKEN_ = 'oauth2-xsrf-token';
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private */
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.KEY_EMAIL_ = 'remoting-email';
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Constants for parameters used in retrieving the OAuth2 credentials.
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @private */
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.SCOPE_ =
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'https://www.googleapis.com/auth/chromoting ' +
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'https://www.googleapis.com/auth/googletalk ' +
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'https://www.googleapis.com/auth/userinfo#email';
492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Configurable URLs/strings.
512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private
522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *  @return {string} OAuth2 redirect URI.
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.getRedirectUri_ = function() {
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return remoting.settings.OAUTH2_REDIRECT_URL;
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *  @return {string} API client ID.
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.getClientId_ = function() {
622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return remoting.settings.OAUTH2_CLIENT_ID;
632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *  @return {string} API client secret.
672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.getClientSecret_ = function() {
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return remoting.settings.OAUTH2_CLIENT_SECRET;
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private
732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *  @return {string} OAuth2 authentication URL.
742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.getOAuth2AuthEndpoint_ = function() {
762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return remoting.settings.OAUTH2_BASE_URL + '/auth';
772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private
802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *  @return {string} OAuth2 token URL.
812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.getOAuth2TokenEndpoint_ = function() {
832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return remoting.settings.OAUTH2_BASE_URL + '/token';
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *  @return {string} OAuth token revocation URL.
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.getOAuth2RevokeTokenEndpoint_ = function() {
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return remoting.settings.OAUTH2_BASE_URL + '/revoke';
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @private
942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *  @return {string} OAuth2 userinfo API URL.
952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.getOAuth2ApiUserInfoEndpoint_ = function() {
972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return remoting.settings.OAUTH2_API_BASE_URL + '/v1/userinfo';
982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** @return {boolean} True if the app is already authenticated. */
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.isAuthenticated = function() {
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.getRefreshToken_()) {
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Removes all storage, and effectively unauthenticates the user.
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.clear = function() {
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_EMAIL_);
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.clearAccessToken_();
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.clearRefreshToken_();
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Sets the refresh token.
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * This method also marks the token as revokable, so that this object will
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * revoke the token when it no longer needs it.
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} token The new refresh token.
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.setRefreshToken = function(token) {
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.setItem(this.KEY_REFRESH_TOKEN_, escape(token));
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.setItem(this.KEY_REFRESH_TOKEN_REVOKABLE_, true);
1317d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_EMAIL_);
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.clearAccessToken_();
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Gets the refresh token.
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * This method also marks the refresh token as not revokable, so that this
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * object will not revoke the token when it no longer needs it. After this
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * object has exported the token, it cannot know whether it is still in use
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * when this object no longer needs it.
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {?string} The refresh token, if authenticated, or NULL.
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.exportRefreshToken = function() {
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_REVOKABLE_);
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return this.getRefreshToken_();
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {?string} The refresh token, if authenticated, or NULL.
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.getRefreshToken_ = function() {
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var value = window.localStorage.getItem(this.KEY_REFRESH_TOKEN_);
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (typeof value == 'string') {
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return unescape(value);
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return null;
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Clears the refresh token.
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.clearRefreshToken_ = function() {
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (window.localStorage.getItem(this.KEY_REFRESH_TOKEN_REVOKABLE_)) {
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.revokeToken_(this.getRefreshToken_());
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_);
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_REVOKABLE_);
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} token The new access token.
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} expiration Expiration time in milliseconds since epoch.
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.setAccessToken = function(token, expiration) {
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var access_token = {'token': token, 'expiration': expiration};
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.setItem(this.KEY_ACCESS_TOKEN_,
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              JSON.stringify(access_token));
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns the current access token, setting it to a invalid value if none
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * existed before.
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {{token: string, expiration: number}} The current access token, or
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * an invalid token if not authenticated.
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.getAccessTokenInternal_ = function() {
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!window.localStorage.getItem(this.KEY_ACCESS_TOKEN_)) {
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Always be able to return structured data.
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.setAccessToken('', 0);
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var accessToken = window.localStorage.getItem(this.KEY_ACCESS_TOKEN_);
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (typeof accessToken == 'string') {
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var result = jsonParseSafe(accessToken);
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (result && 'token' in result && 'expiration' in result) {
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return /** @type {{token: string, expiration: number}} */ result;
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  console.log('Invalid access token stored.');
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return {'token': '', 'expiration': 0};
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns true if the access token is expired, or otherwise invalid.
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Will throw if !isAuthenticated().
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {boolean} True if a new access token is needed.
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.needsNewAccessToken_ = function() {
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!this.isAuthenticated()) {
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    throw 'Not Authenticated.';
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var access_token = this.getAccessTokenInternal_();
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!access_token['token']) {
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (Date.now() > access_token['expiration']) {
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.clearAccessToken_ = function() {
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.localStorage.removeItem(this.KEY_ACCESS_TOKEN_);
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Update state based on token response from the OAuth2 /token endpoint.
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(XMLHttpRequest, string): void} onDone Callback to invoke on
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     completion.
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {XMLHttpRequest} xhr The XHR object for this request.
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.processTokenResponse_ = function(onDone, xhr) {
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /** @type {string} */
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var accessToken = '';
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (xhr.status == 200) {
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try {
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // Don't use jsonParseSafe here unless you move the definition out of
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // remoting.js, otherwise this won't work from the OAuth trampoline.
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var tokens = JSON.parse(xhr.responseText);
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if ('refresh_token' in tokens) {
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.setRefreshToken(tokens['refresh_token']);
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // Offset by 120 seconds so that we can guarantee that the token
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // we return will be valid for at least 2 minutes.
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // If the access token is to be useful, this object must make some
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // guarantee as to how long the token will be valid for.
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // The choice of 2 minutes is arbitrary, but that length of time
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // is part of the contract satisfied by callWithToken().
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // Offset by a further 30 seconds to account for RTT issues.
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      accessToken = /** @type {string} */ (tokens['access_token']);
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.setAccessToken(accessToken,
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          (tokens['expires_in'] - (120 + 30)) * 1000 + Date.now());
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } catch (err) {
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      console.error('Invalid "token" response from server:',
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    /** @type {*} */ (err));
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    console.error('Failed to get tokens. Status: ' + xhr.status +
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  ' response: ' + xhr.responseText);
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  onDone(xhr, accessToken);
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Asynchronously retrieves a new access token from the server.
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Will throw if !isAuthenticated().
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(XMLHttpRequest): void} onDone Callback to invoke on
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     completion.
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.refreshAccessToken_ = function(onDone) {
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!this.isAuthenticated()) {
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    throw 'Not Authenticated.';
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var parameters = {
3002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    'client_id': this.getClientId_(),
3012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    'client_secret': this.getClientSecret_(),
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'refresh_token': this.getRefreshToken_(),
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'grant_type': 'refresh_token'
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  remoting.xhr.post(this.getOAuth2TokenEndpoint_(),
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    this.processTokenResponse_.bind(this, onDone),
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    parameters);
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Redirect page to get a new OAuth2 Refresh Token.
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.doAuthRedirect = function() {
317c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  var xsrf_token = remoting.generateXsrfToken();
3182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  window.localStorage.setItem(this.KEY_XSRF_TOKEN_, xsrf_token);
3192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var GET_CODE_URL = this.getOAuth2AuthEndpoint_() + '?' +
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    remoting.xhr.urlencodeParamHash({
3212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'client_id': this.getClientId_(),
3222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'redirect_uri': this.getRedirectUri_(),
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'scope': this.SCOPE_,
3242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'state': xsrf_token,
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'response_type': 'code',
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'access_type': 'offline',
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'approval_prompt': 'force'
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        });
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  window.location.replace(GET_CODE_URL);
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Asynchronously exchanges an authorization code for a refresh token.
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} code The new refresh token.
3362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {string} state The state parameter received from the OAuth redirect.
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(XMLHttpRequest):void} onDone Callback to invoke on
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     completion.
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)remoting.OAuth2.prototype.exchangeCodeForToken = function(code, state, onDone) {
3422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var xsrf_token = window.localStorage.getItem(this.KEY_XSRF_TOKEN_);
3432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  window.localStorage.removeItem(this.KEY_XSRF_TOKEN_);
3442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (xsrf_token == undefined || state != xsrf_token) {
3452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Invalid XSRF token, or unexpected OAuth2 redirect. Abort.
3462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    onDone(null);
3472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var parameters = {
3492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    'client_id': this.getClientId_(),
3502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    'client_secret': this.getClientSecret_(),
3512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    'redirect_uri': this.getRedirectUri_(),
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'code': code,
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'grant_type': 'authorization_code'
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
3552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  remoting.xhr.post(this.getOAuth2TokenEndpoint_(),
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    this.processTokenResponse_.bind(this, onDone),
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    parameters);
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Interprets unexpected HTTP response codes to authentication XMLHttpRequests.
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * The caller should handle the usual expected responses (200, 400) separately.
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} xhrStatus Status (HTTP response code) of the XMLHttpRequest.
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {remoting.Error} An error code to be raised.
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.interpretUnexpectedXhrStatus_ = function(xhrStatus) {
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Return AUTHENTICATION_FAILED by default, so that the user can try to
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // recover from an unexpected failure by signing in again.
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /** @type {remoting.Error} */
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var error = remoting.Error.AUTHENTICATION_FAILED;
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (xhrStatus == 502 || xhrStatus == 503) {
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    error = remoting.Error.SERVICE_UNAVAILABLE;
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else if (xhrStatus == 0) {
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    error = remoting.Error.NETWORK_FAILURE;
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    console.warn('Unexpected authentication response code: ' + xhrStatus);
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return error;
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Revokes a refresh or an access token.
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string?} token An access or refresh token.
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.revokeToken_ = function(token) {
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!token || (token.length == 0)) {
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var parameters = { 'token': token };
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /** @param {XMLHttpRequest} xhr The XHR reply. */
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var processResponse = function(xhr) {
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (xhr.status != 200) {
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      console.log('Failed to revoke token. Status: ' + xhr.status +
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  ' ; response: ' + xhr.responseText + ' ; xhr: ', xhr);
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
4032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  remoting.xhr.post(this.getOAuth2RevokeTokenEndpoint_(),
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    processResponse,
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    parameters);
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Call a function with an access token, refreshing it first if necessary.
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * The access token will remain valid for at least 2 minutes.
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(string):void} onOk Function to invoke with access token if
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     an access token was successfully retrieved.
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(remoting.Error):void} onError Function to invoke with an
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     error code on failure.
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.callWithToken = function(onOk, onError) {
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.isAuthenticated()) {
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (this.needsNewAccessToken_()) {
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.refreshAccessToken_(this.onRefreshToken_.bind(this, onOk, onError));
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      onOk(this.getAccessTokenInternal_()['token']);
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    onError(remoting.Error.NOT_AUTHENTICATED);
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Process token refresh results and notify caller.
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(string):void} onOk Function to invoke with access token if
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     an access token was successfully retrieved.
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(remoting.Error):void} onError Function to invoke with an
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     error code on failure.
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {XMLHttpRequest} xhr The result of the refresh operation.
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} accessToken The fresh access token.
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.onRefreshToken_ = function(onOk, onError, xhr,
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                                     accessToken) {
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /** @type {remoting.Error} */
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var error = remoting.Error.UNEXPECTED;
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (xhr.status == 200) {
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    onOk(accessToken);
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else if (xhr.status == 400) {
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var result =
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        /** @type {{error: string}} */ (jsonParseSafe(xhr.responseText));
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (result && result.error == 'invalid_grant') {
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      error = remoting.Error.AUTHENTICATION_FAILED;
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    error = this.interpretUnexpectedXhrStatus_(xhr.status);
4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  onError(error);
4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Get the user's email address.
4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(string):void} onOk Callback invoked when the email
4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     address is available.
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(remoting.Error):void} onError Callback invoked if an
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     error occurs.
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {void} Nothing.
4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.getEmail = function(onOk, onError) {
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var cached = window.localStorage.getItem(this.KEY_EMAIL_);
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (typeof cached == 'string') {
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    onOk(cached);
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /** @type {remoting.OAuth2} */
4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var that = this;
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /** @param {XMLHttpRequest} xhr The XHR response. */
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var onResponse = function(xhr) {
4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var email = null;
4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (xhr.status == 200) {
4812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      var result = jsonParseSafe(xhr.responseText);
4822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if (result && 'email' in result) {
4832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        window.localStorage.setItem(that.KEY_EMAIL_, result['email']);
4842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        onOk(result['email']);
4852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return;
4862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      } else {
4872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        console.error(
4882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            'Cannot parse userinfo response: ', xhr.responseText, xhr);
4892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        onError(remoting.Error.UNEXPECTED);
4902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return;
4912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      }
4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    console.error('Unable to get email address:', xhr.status, xhr);
4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (xhr.status == 401) {
4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      onError(remoting.Error.AUTHENTICATION_FAILED);
4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      onError(that.interpretUnexpectedXhrStatus_(xhr.status));
4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /** @param {string} token The access token. */
5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var getEmailFromToken = function(token) {
5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var headers = { 'Authorization': 'OAuth ' + token };
5042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    remoting.xhr.get(that.getOAuth2ApiUserInfoEndpoint_(),
5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     onResponse, '', headers);
5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
5075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.callWithToken(getEmailFromToken, onError);
5095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
5125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * If the user's email address is cached, return it, otherwise return null.
5135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {?string} The email address, if it has been cached by a previous call
5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     to getEmail, otherwise null.
5165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)remoting.OAuth2.prototype.getCachedEmail = function() {
5185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var value = window.localStorage.getItem(this.KEY_EMAIL_);
5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (typeof value == 'string') {
5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return value;
5215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return null;
5235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
524