Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / webapp / base / js / oauth2_api_impl.js
blobde5d9df49e6c25165888c67f2c12705337d22686
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.
5 /**
6  * @fileoverview
7  * OAuth2 API flow implementations.
8  */
10 /** @suppress {duplicate} */
11 var remoting = remoting || {};
13 (function() {
15 'use strict';
17 /**
18  * @constructor
19  * @implements {remoting.OAuth2Api}
20  */
21 remoting.OAuth2ApiImpl = function() {
24 /** @private
25  *  @return {string} OAuth2 token URL.
26  */
27 remoting.OAuth2ApiImpl.prototype.getOAuth2TokenEndpoint_ = function() {
28   return remoting.settings.OAUTH2_BASE_URL + '/token';
31 /** @private
32  *  @return {string} OAuth2 userinfo API URL.
33  */
34 remoting.OAuth2ApiImpl.prototype.getOAuth2ApiUserInfoEndpoint_ = function() {
35   return remoting.settings.OAUTH2_API_BASE_URL + '/v1/userinfo';
39 /**
40  * Interprets HTTP error responses in authentication XMLHttpRequests.
41  *
42  * @private
43  * @param {number} xhrStatus Status (HTTP response code) of the XMLHttpRequest.
44  * @return {!remoting.Error} An error code to be raised.
45  */
46 remoting.OAuth2ApiImpl.prototype.interpretXhrStatus_ =
47     function(xhrStatus) {
48   // Return AUTHENTICATION_FAILED by default, so that the user can try to
49   // recover from an unexpected failure by signing in again.
50   /** @type {!remoting.Error} */
51   var error = new remoting.Error(remoting.Error.Tag.AUTHENTICATION_FAILED);
52   if (xhrStatus == 400 || xhrStatus == 401 || xhrStatus == 403) {
53     error = new remoting.Error(remoting.Error.Tag.AUTHENTICATION_FAILED);
54   } else if (xhrStatus == 502 || xhrStatus == 503) {
55     error = new remoting.Error(remoting.Error.Tag.SERVICE_UNAVAILABLE);
56   } else if (xhrStatus == 0) {
57     error = new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE);
58   } else {
59     console.warn('Unexpected authentication response code: ' + xhrStatus);
60   }
61   return error;
64 /**
65  * Asynchronously retrieves a new access token from the server.
66  *
67  * @param {function(string, number): void} onDone Callback to invoke when
68  *     the access token and expiration time are successfully fetched.
69  * @param {function(!remoting.Error):void} onError Callback invoked if an
70  *     error occurs.
71  * @param {string} clientId OAuth2 client ID.
72  * @param {string} clientSecret OAuth2 client secret.
73  * @param {string} refreshToken OAuth2 refresh token to be redeemed.
74  * @return {void} Nothing.
75  */
76 remoting.OAuth2ApiImpl.prototype.refreshAccessToken = function(
77     onDone, onError, clientId, clientSecret, refreshToken) {
78   /** @param {!remoting.Xhr.Response} response */
79   var onResponse = function(response) {
80     if (response.status == 200) {
81       try {
82         // Don't use base.jsonParseSafe here unless you also include base.js,
83         // otherwise this won't work from the OAuth trampoline.
84         // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
85         var tokens = JSON.parse(response.getText());
86         onDone(tokens['access_token'], tokens['expires_in']);
87       } catch (/** @type {Error} */ err) {
88         console.error('Invalid "token" response from server:', err);
89         onError(remoting.Error.unexpected());
90       }
91     } else {
92       console.error('Failed to refresh token. Status: ' + response.status +
93                     ' response: ' + response.getText());
94       onError(remoting.Error.fromHttpStatus(response.status));
95     }
96   };
98   new remoting.Xhr({
99     method: 'POST',
100     url: this.getOAuth2TokenEndpoint_(),
101     formContent: {
102       'client_id': clientId,
103       'client_secret': clientSecret,
104       'refresh_token': refreshToken,
105       'grant_type': 'refresh_token'
106     }
107   }).start().then(onResponse);
111  * Asynchronously exchanges an authorization code for access and refresh tokens.
113  * @param {function(string, string, number): void} onDone Callback to
114  *     invoke when the refresh token, access token and access token expiration
115  *     time are successfully fetched.
116  * @param {function(!remoting.Error):void} onError Callback invoked if an
117  *     error occurs.
118  * @param {string} clientId OAuth2 client ID.
119  * @param {string} clientSecret OAuth2 client secret.
120  * @param {string} code OAuth2 authorization code.
121  * @param {string} redirectUri Redirect URI used to obtain this code.
122  * @return {void} Nothing.
123  */
124 remoting.OAuth2ApiImpl.prototype.exchangeCodeForTokens = function(
125     onDone, onError, clientId, clientSecret, code, redirectUri) {
126   /** @param {!remoting.Xhr.Response} response */
127   var onResponse = function(response) {
128     if (response.status == 200) {
129       try {
130         // Don't use base.jsonParseSafe here unless you also include base.js,
131         // otherwise this won't work from the OAuth trampoline.
132         // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
133         var tokens = JSON.parse(response.getText());
134         onDone(tokens['refresh_token'],
135                tokens['access_token'], tokens['expires_in']);
136       } catch (/** @type {Error} */ err) {
137         console.error('Invalid "token" response from server:', err);
138         onError(remoting.Error.unexpected());
139       }
140     } else {
141       console.error('Failed to exchange code for token. Status: ' +
142                     response.status + ' response: ' + response.getText());
143       onError(remoting.Error.fromHttpStatus(response.status));
144     }
145   };
147   new remoting.Xhr({
148     method: 'POST',
149     url: this.getOAuth2TokenEndpoint_(),
150     formContent: {
151       'client_id': clientId,
152       'client_secret': clientSecret,
153       'redirect_uri': redirectUri,
154       'code': code,
155       'grant_type': 'authorization_code'
156     }
157   }).start().then(onResponse);
161  * Get the user's email address.
163  * @param {function(string):void} onDone Callback invoked when the email
164  *     address is available.
165  * @param {function(!remoting.Error):void} onError Callback invoked if an
166  *     error occurs.
167  * @param {string} token Access token.
168  * @return {void} Nothing.
169  */
170 remoting.OAuth2ApiImpl.prototype.getEmail = function(onDone, onError, token) {
171   /** @param {!remoting.Xhr.Response} response */
172   var onResponse = function(response) {
173     if (response.status == 200) {
174       try {
175         var result = JSON.parse(response.getText());
176         onDone(result['email']);
177       } catch (/** @type {Error} */ err) {
178         console.error('Invalid "userinfo" response from server:', err);
179         onError(remoting.Error.unexpected());
180       }
181     } else {
182       console.error('Failed to get email. Status: ' + response.status +
183                     ' response: ' + response.getText());
184       onError(remoting.Error.fromHttpStatus(response.status));
185     }
186   };
187   new remoting.Xhr({
188     method: 'GET',
189     url: this.getOAuth2ApiUserInfoEndpoint_(),
190     oauthToken: token
191   }).start().then(onResponse);
195  * Get the user's email address and full name.
197  * @param {function(string, string):void} onDone Callback invoked when the email
198  *     address and full name are available.
199  * @param {function(!remoting.Error):void} onError Callback invoked if an
200  *     error occurs.
201  * @param {string} token Access token.
202  * @return {void} Nothing.
203  */
204 remoting.OAuth2ApiImpl.prototype.getUserInfo =
205     function(onDone, onError, token) {
206   /** @param {!remoting.Xhr.Response} response */
207   var onResponse = function(response) {
208     if (response.status == 200) {
209       try {
210         var result = JSON.parse(response.getText());
211         onDone(result['email'], result['name']);
212       } catch (/** @type {Error} */ err) {
213         console.error('Invalid "userinfo" response from server:', err);
214         onError(remoting.Error.unexpected());
215       }
216     } else {
217       console.error('Failed to get user info. Status: ' + response.status +
218                     ' response: ' + response.getText());
219       onError(remoting.Error.fromHttpStatus(response.status));
220     }
221   };
222   new remoting.Xhr({
223     method: 'GET',
224     url: this.getOAuth2ApiUserInfoEndpoint_(),
225     oauthToken: token
226   }).start().then(onResponse);
229 /** @returns {!remoting.Error} */
230 function fromHttpStatus(/** number */ status) {
231   var error = remoting.Error.fromHttpStatus(status);
232   if (error === remoting.Error.unexpected()) {
233     // Return AUTHENTICATION_FAILED by default, so that the user can try to
234     // recover from an unexpected failure by signing in again.
235     return new remoting.Error(remoting.Error.Tag.AUTHENTICATION_FAILED);
236   }
237   return error;
240 /** @type {remoting.OAuth2Api} */
241 remoting.oauth2Api = new remoting.OAuth2ApiImpl();
243 })();