Home | History | Annotate | Download | only in webapp
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @fileoverview
      7  * OAuth2 API flow implementations.
      8  */
      9 
     10 'use strict';
     11 
     12 /** @suppress {duplicate} */
     13 var remoting = remoting || {};
     14 
     15 /** @constructor */
     16 remoting.OAuth2Api = function() {
     17 };
     18 
     19 /** @private
     20  *  @return {string} OAuth2 token URL.
     21  */
     22 remoting.OAuth2Api.getOAuth2TokenEndpoint_ = function() {
     23   return remoting.settings.OAUTH2_BASE_URL + '/token';
     24 };
     25 
     26 /** @private
     27  *  @return {string} OAuth2 userinfo API URL.
     28  */
     29 remoting.OAuth2Api.getOAuth2ApiUserInfoEndpoint_ = function() {
     30   return remoting.settings.OAUTH2_API_BASE_URL + '/v1/userinfo';
     31 };
     32 
     33 
     34 /**
     35  * Interprets HTTP error responses in authentication XMLHttpRequests.
     36  *
     37  * @private
     38  * @param {number} xhrStatus Status (HTTP response code) of the XMLHttpRequest.
     39  * @return {remoting.Error} An error code to be raised.
     40  */
     41 remoting.OAuth2Api.interpretXhrStatus_ =
     42     function(xhrStatus) {
     43   // Return AUTHENTICATION_FAILED by default, so that the user can try to
     44   // recover from an unexpected failure by signing in again.
     45   /** @type {remoting.Error} */
     46   var error = remoting.Error.AUTHENTICATION_FAILED;
     47   if (xhrStatus == 400 || xhrStatus == 401 || xhrStatus == 403) {
     48     error = remoting.Error.AUTHENTICATION_FAILED;
     49   } else if (xhrStatus == 502 || xhrStatus == 503) {
     50     error = remoting.Error.SERVICE_UNAVAILABLE;
     51   } else if (xhrStatus == 0) {
     52     error = remoting.Error.NETWORK_FAILURE;
     53   } else {
     54     console.warn('Unexpected authentication response code: ' + xhrStatus);
     55   }
     56   return error;
     57 };
     58 
     59 /**
     60  * Asynchronously retrieves a new access token from the server.
     61  *
     62  * @param {function(string, number): void} onDone Callback to invoke when
     63  *     the access token and expiration time are successfully fetched.
     64  * @param {function(remoting.Error):void} onError Callback invoked if an
     65  *     error occurs.
     66  * @param {string} clientId OAuth2 client ID.
     67  * @param {string} clientSecret OAuth2 client secret.
     68  * @param {string} refreshToken OAuth2 refresh token to be redeemed.
     69  * @return {void} Nothing.
     70  */
     71 remoting.OAuth2Api.refreshAccessToken = function(
     72     onDone, onError, clientId, clientSecret, refreshToken) {
     73   /** @param {XMLHttpRequest} xhr */
     74   var onResponse = function(xhr) {
     75     if (xhr.status == 200) {
     76       try {
     77         // Don't use jsonParseSafe here unless you move the definition out of
     78         // remoting.js, otherwise this won't work from the OAuth trampoline.
     79         // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
     80         var tokens = JSON.parse(xhr.responseText);
     81         onDone(tokens['access_token'], tokens['expires_in']);
     82       } catch (err) {
     83         console.error('Invalid "token" response from server:',
     84                       /** @type {*} */ (err));
     85         onError(remoting.Error.UNEXPECTED);
     86       }
     87     } else {
     88       console.error('Failed to refresh token. Status: ' + xhr.status +
     89                     ' response: ' + xhr.responseText);
     90       onError(remoting.OAuth2Api.interpretXhrStatus_(xhr.status));
     91     }
     92   };
     93 
     94   var parameters = {
     95     'client_id': clientId,
     96     'client_secret': clientSecret,
     97     'refresh_token': refreshToken,
     98     'grant_type': 'refresh_token'
     99   };
    100 
    101   remoting.xhr.post(remoting.OAuth2Api.getOAuth2TokenEndpoint_(),
    102                     onResponse, parameters);
    103 };
    104 
    105 /**
    106  * Asynchronously exchanges an authorization code for access and refresh tokens.
    107  *
    108  * @param {function(string, string, number): void} onDone Callback to
    109  *     invoke when the refresh token, access token and access token expiration
    110  *     time are successfully fetched.
    111  * @param {function(remoting.Error):void} onError Callback invoked if an
    112  *     error occurs.
    113  * @param {string} clientId OAuth2 client ID.
    114  * @param {string} clientSecret OAuth2 client secret.
    115  * @param {string} code OAuth2 authorization code.
    116  * @param {string} redirectUri Redirect URI used to obtain this code.
    117  * @return {void} Nothing.
    118  */
    119 remoting.OAuth2Api.exchangeCodeForTokens = function(
    120     onDone, onError, clientId, clientSecret, code, redirectUri) {
    121   /** @param {XMLHttpRequest} xhr */
    122   var onResponse = function(xhr) {
    123     if (xhr.status == 200) {
    124       try {
    125         // Don't use jsonParseSafe here unless you move the definition out of
    126         // remoting.js, otherwise this won't work from the OAuth trampoline.
    127         // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
    128         var tokens = JSON.parse(xhr.responseText);
    129         onDone(tokens['refresh_token'],
    130                tokens['access_token'], tokens['expires_in']);
    131       } catch (err) {
    132         console.error('Invalid "token" response from server:',
    133                       /** @type {*} */ (err));
    134         onError(remoting.Error.UNEXPECTED);
    135       }
    136     } else {
    137       console.error('Failed to exchange code for token. Status: ' + xhr.status +
    138                     ' response: ' + xhr.responseText);
    139       onError(remoting.OAuth2Api.interpretXhrStatus_(xhr.status));
    140     }
    141   };
    142 
    143   var parameters = {
    144     'client_id': clientId,
    145     'client_secret': clientSecret,
    146     'redirect_uri': redirectUri,
    147     'code': code,
    148     'grant_type': 'authorization_code'
    149   };
    150   remoting.xhr.post(remoting.OAuth2Api.getOAuth2TokenEndpoint_(),
    151                     onResponse, parameters);
    152 };
    153 
    154 /**
    155  * Get the user's email address.
    156  *
    157  * @param {function(string):void} onDone Callback invoked when the email
    158  *     address is available.
    159  * @param {function(remoting.Error):void} onError Callback invoked if an
    160  *     error occurs.
    161  * @param {string} token Access token.
    162  * @return {void} Nothing.
    163  */
    164 remoting.OAuth2Api.getEmail = function(onDone, onError, token) {
    165   /** @param {XMLHttpRequest} xhr */
    166   var onResponse = function(xhr) {
    167     if (xhr.status == 200) {
    168       try {
    169         var result = JSON.parse(xhr.responseText);
    170         onDone(result['email']);
    171       } catch (err) {
    172         console.error('Invalid "userinfo" response from server:',
    173                       /** @type {*} */ (err));
    174         onError(remoting.Error.UNEXPECTED);
    175       }
    176     } else {
    177       console.error('Failed to get email. Status: ' + xhr.status +
    178                     ' response: ' + xhr.responseText);
    179       onError(remoting.OAuth2Api.interpretXhrStatus_(xhr.status));
    180     }
    181   };
    182   var headers = { 'Authorization': 'OAuth ' + token };
    183   remoting.xhr.get(remoting.OAuth2Api.getOAuth2ApiUserInfoEndpoint_(),
    184                    onResponse, '', headers);
    185 };
    186