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