| // Copyright 2013 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. |
| |
| /** |
| * @fileoverview |
| * OAuth2 API flow implementations. |
| */ |
| |
| 'use strict'; |
| |
| /** @suppress {duplicate} */ |
| var remoting = remoting || {}; |
| |
| /** @constructor */ |
| remoting.OAuth2Api = function() { |
| }; |
| |
| /** @private |
| * @return {string} OAuth2 token URL. |
| */ |
| remoting.OAuth2Api.getOAuth2TokenEndpoint_ = function() { |
| return remoting.settings.OAUTH2_BASE_URL + '/token'; |
| }; |
| |
| /** @private |
| * @return {string} OAuth token revocation URL. |
| */ |
| remoting.OAuth2Api.getOAuth2RevokeTokenEndpoint_ = function() { |
| return remoting.settings.OAUTH2_BASE_URL + '/revoke'; |
| }; |
| |
| /** @private |
| * @return {string} OAuth2 userinfo API URL. |
| */ |
| remoting.OAuth2Api.getOAuth2ApiUserInfoEndpoint_ = function() { |
| return remoting.settings.OAUTH2_API_BASE_URL + '/v1/userinfo'; |
| }; |
| |
| |
| /** |
| * Interprets HTTP error responses in authentication XMLHttpRequests. |
| * |
| * @private |
| * @param {number} xhrStatus Status (HTTP response code) of the XMLHttpRequest. |
| * @return {remoting.Error} An error code to be raised. |
| */ |
| remoting.OAuth2Api.interpretXhrStatus_ = |
| function(xhrStatus) { |
| // Return AUTHENTICATION_FAILED by default, so that the user can try to |
| // recover from an unexpected failure by signing in again. |
| /** @type {remoting.Error} */ |
| var error = remoting.Error.AUTHENTICATION_FAILED; |
| if (xhrStatus == 400 || xhrStatus == 401 || xhrStatus == 403) { |
| error = remoting.Error.AUTHENTICATION_FAILED; |
| } else if (xhrStatus == 502 || xhrStatus == 503) { |
| error = remoting.Error.SERVICE_UNAVAILABLE; |
| } else if (xhrStatus == 0) { |
| error = remoting.Error.NETWORK_FAILURE; |
| } else { |
| console.warn('Unexpected authentication response code: ' + xhrStatus); |
| } |
| return error; |
| }; |
| |
| /** |
| * Asynchronously retrieves a new access token from the server. |
| * |
| * @param {function(string, number): void} onDone Callback to invoke when |
| * the access token and expiration time are successfully fetched. |
| * @param {function(remoting.Error):void} onError Callback invoked if an |
| * error occurs. |
| * @param {string} clientId OAuth2 client ID. |
| * @param {string} clientSecret OAuth2 client secret. |
| * @param {string} refreshToken OAuth2 refresh token to be redeemed. |
| * @return {void} Nothing. |
| */ |
| remoting.OAuth2Api.refreshAccessToken = function( |
| onDone, onError, clientId, clientSecret, refreshToken) { |
| /** @param {XMLHttpRequest} xhr */ |
| var onResponse = function(xhr) { |
| if (xhr.status == 200) { |
| try { |
| // Don't use jsonParseSafe here unless you move the definition out of |
| // remoting.js, otherwise this won't work from the OAuth trampoline. |
| // TODO(jamiewalch): Fix this once we're no longer using the trampoline. |
| var tokens = JSON.parse(xhr.responseText); |
| onDone(tokens['access_token'], tokens['expires_in']); |
| } catch (err) { |
| console.error('Invalid "token" response from server:', |
| /** @type {*} */ (err)); |
| onError(remoting.Error.UNEXPECTED); |
| } |
| } else { |
| console.error('Failed to refresh token. Status: ' + xhr.status + |
| ' response: ' + xhr.responseText); |
| onError(remoting.OAuth2Api.interpretXhrStatus_(xhr.status)); |
| } |
| }; |
| |
| var parameters = { |
| 'client_id': clientId, |
| 'client_secret': clientSecret, |
| 'refresh_token': refreshToken, |
| 'grant_type': 'refresh_token' |
| }; |
| |
| remoting.xhr.post(remoting.OAuth2Api.getOAuth2TokenEndpoint_(), |
| onResponse, parameters); |
| }; |
| |
| /** |
| * Asynchronously exchanges an authorization code for access and refresh tokens. |
| * |
| * @param {function(string, string, number): void} onDone Callback to |
| * invoke when the refresh token, access token and access token expiration |
| * time are successfully fetched. |
| * @param {function(remoting.Error):void} onError Callback invoked if an |
| * error occurs. |
| * @param {string} clientId OAuth2 client ID. |
| * @param {string} clientSecret OAuth2 client secret. |
| * @param {string} code OAuth2 authorization code. |
| * @param {string} redirectUri Redirect URI used to obtain this code. |
| * @return {void} Nothing. |
| */ |
| remoting.OAuth2Api.exchangeCodeForTokens = function( |
| onDone, onError, clientId, clientSecret, code, redirectUri) { |
| /** @param {XMLHttpRequest} xhr */ |
| var onResponse = function(xhr) { |
| if (xhr.status == 200) { |
| try { |
| // Don't use jsonParseSafe here unless you move the definition out of |
| // remoting.js, otherwise this won't work from the OAuth trampoline. |
| // TODO(jamiewalch): Fix this once we're no longer using the trampoline. |
| var tokens = JSON.parse(xhr.responseText); |
| onDone(tokens['refresh_token'], |
| tokens['access_token'], tokens['expires_in']); |
| } catch (err) { |
| console.error('Invalid "token" response from server:', |
| /** @type {*} */ (err)); |
| onError(remoting.Error.UNEXPECTED); |
| } |
| } else { |
| console.error('Failed to exchange code for token. Status: ' + xhr.status + |
| ' response: ' + xhr.responseText); |
| onError(remoting.OAuth2Api.interpretXhrStatus_(xhr.status)); |
| } |
| }; |
| |
| var parameters = { |
| 'client_id': clientId, |
| 'client_secret': clientSecret, |
| 'redirect_uri': redirectUri, |
| 'code': code, |
| 'grant_type': 'authorization_code' |
| }; |
| remoting.xhr.post(remoting.OAuth2Api.getOAuth2TokenEndpoint_(), |
| onResponse, parameters); |
| }; |
| |
| /** |
| * Get the user's email address. |
| * |
| * @param {function(string):void} onDone Callback invoked when the email |
| * address is available. |
| * @param {function(remoting.Error):void} onError Callback invoked if an |
| * error occurs. |
| * @param {string} token Access token. |
| * @return {void} Nothing. |
| */ |
| remoting.OAuth2Api.getEmail = function(onDone, onError, token) { |
| /** @param {XMLHttpRequest} xhr */ |
| var onResponse = function(xhr) { |
| if (xhr.status == 200) { |
| try { |
| var result = JSON.parse(xhr.responseText); |
| onDone(result['email']); |
| } catch (err) { |
| console.error('Invalid "userinfo" response from server:', |
| /** @type {*} */ (err)); |
| onError(remoting.Error.UNEXPECTED); |
| } |
| } else { |
| console.error('Failed to get email. Status: ' + xhr.status + |
| ' response: ' + xhr.responseText); |
| onError(remoting.OAuth2Api.interpretXhrStatus_(xhr.status)); |
| } |
| }; |
| var headers = { 'Authorization': 'OAuth ' + token }; |
| remoting.xhr.get(remoting.OAuth2Api.getOAuth2ApiUserInfoEndpoint_(), |
| onResponse, '', headers); |
| }; |
| |
| /** |
| * Revokes a refresh or an access token. |
| * |
| * @param {function():void} onDone Callback invoked when the token is |
| * revoked. |
| * @param {function(remoting.Error):void} onError Callback invoked if an |
| * error occurs. |
| * @param {string} token An access or refresh token. |
| * @return {void} Nothing. |
| */ |
| remoting.OAuth2Api.revokeToken = function(onDone, onError, token) { |
| /** @param {XMLHttpRequest} xhr */ |
| var onResponse = function(xhr) { |
| if (xhr.status == 200) { |
| onDone(); |
| } else { |
| console.error('Failed to revoke token. Status: ' + xhr.status + |
| ' response: ' + xhr.responseText); |
| onError(remoting.OAuth2Api.interpretXhrStatus_(xhr.status)); |
| } |
| }; |
| |
| var parameters = { 'token': token }; |
| remoting.xhr.post(remoting.OAuth2Api.getOAuth2RevokeTokenEndpoint_(), |
| onResponse, parameters); |
| }; |