Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview | 6 * @fileoverview |
| 7 * OAuth2 API flow implementations. | 7 * OAuth2 API flow implementations. |
| 8 */ | 8 */ |
| 9 | 9 |
| 10 'use strict'; | 10 'use strict'; |
| 11 | 11 |
| 12 /** @suppress {duplicate} */ | 12 /** @suppress {duplicate} */ |
| 13 var remoting = remoting || {}; | 13 var remoting = remoting || {}; |
| 14 | 14 |
| 15 /** @constructor */ | 15 /** @interface */ |
| 16 remoting.OAuth2Api = function() { | 16 remoting.OAuth2Api = function() { |
| 17 }; | 17 }; |
| 18 | 18 |
| 19 /** @private | |
| 20 * @return {string} OAuth2 token URL. | |
| 21 */ | |
| 22 remoting.OAuth2Api.prototype.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.prototype.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.prototype.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 /** | 19 /** |
| 60 * Asynchronously retrieves a new access token from the server. | 20 * Asynchronously retrieves a new access token from the server. |
| 61 * | 21 * |
| 62 * @param {function(string, number): void} onDone Callback to invoke when | 22 * @param {function(string, number): void} onDone Callback to invoke when |
| 63 * the access token and expiration time are successfully fetched. | 23 * the access token and expiration time are successfully fetched. |
| 64 * @param {function(remoting.Error):void} onError Callback invoked if an | 24 * @param {function(remoting.Error):void} onError Callback invoked if an |
| 65 * error occurs. | 25 * error occurs. |
| 66 * @param {string} clientId OAuth2 client ID. | 26 * @param {string} clientId OAuth2 client ID. |
| 67 * @param {string} clientSecret OAuth2 client secret. | 27 * @param {string} clientSecret OAuth2 client secret. |
| 68 * @param {string} refreshToken OAuth2 refresh token to be redeemed. | 28 * @param {string} refreshToken OAuth2 refresh token to be redeemed. |
| 69 * @return {void} Nothing. | 29 * @return {void} Nothing. |
| 70 */ | 30 */ |
| 71 remoting.OAuth2Api.prototype.refreshAccessToken = function( | 31 remoting.OAuth2Api.prototype.refreshAccessToken = function( |
| 72 onDone, onError, clientId, clientSecret, refreshToken) { | 32 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 base.jsonParseSafe here unless you also include base.js, | |
| 78 // 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.Error.fromHttpError(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(this.getOAuth2TokenEndpoint_(), onResponse, parameters); | |
| 102 }; | 33 }; |
| 103 | 34 |
| 104 /** | 35 /** |
| 105 * Asynchronously exchanges an authorization code for access and refresh tokens. | 36 * Asynchronously exchanges an authorization code for access and refresh tokens. |
| 106 * | 37 * |
| 107 * @param {function(string, string, number): void} onDone Callback to | 38 * @param {function(string, string, number): void} onDone Callback to |
| 108 * invoke when the refresh token, access token and access token expiration | 39 * invoke when the refresh token, access token and access token expiration |
| 109 * time are successfully fetched. | 40 * time are successfully fetched. |
| 110 * @param {function(remoting.Error):void} onError Callback invoked if an | 41 * @param {function(remoting.Error):void} onError Callback invoked if an |
| 111 * error occurs. | 42 * error occurs. |
| 112 * @param {string} clientId OAuth2 client ID. | 43 * @param {string} clientId OAuth2 client ID. |
| 113 * @param {string} clientSecret OAuth2 client secret. | 44 * @param {string} clientSecret OAuth2 client secret. |
| 114 * @param {string} code OAuth2 authorization code. | 45 * @param {string} code OAuth2 authorization code. |
| 115 * @param {string} redirectUri Redirect URI used to obtain this code. | 46 * @param {string} redirectUri Redirect URI used to obtain this code. |
| 116 * @return {void} Nothing. | 47 * @return {void} Nothing. |
| 117 */ | 48 */ |
| 118 remoting.OAuth2Api.prototype.exchangeCodeForTokens = function( | 49 remoting.OAuth2Api.prototype.exchangeCodeForTokens = function( |
| 119 onDone, onError, clientId, clientSecret, code, redirectUri) { | 50 onDone, onError, clientId, clientSecret, code, redirectUri) { |
| 120 /** @param {XMLHttpRequest} xhr */ | |
| 121 var onResponse = function(xhr) { | |
| 122 if (xhr.status == 200) { | |
| 123 try { | |
| 124 // Don't use base.jsonParseSafe here unless you also include base.js, | |
| 125 // otherwise this won't work from the OAuth trampoline. | |
| 126 // TODO(jamiewalch): Fix this once we're no longer using the trampoline. | |
| 127 var tokens = JSON.parse(xhr.responseText); | |
| 128 onDone(tokens['refresh_token'], | |
| 129 tokens['access_token'], tokens['expires_in']); | |
| 130 } catch (err) { | |
| 131 console.error('Invalid "token" response from server:', | |
| 132 /** @type {*} */ (err)); | |
| 133 onError(remoting.Error.UNEXPECTED); | |
| 134 } | |
| 135 } else { | |
| 136 console.error('Failed to exchange code for token. Status: ' + xhr.status + | |
| 137 ' response: ' + xhr.responseText); | |
| 138 onError(remoting.Error.fromHttpError(xhr.status)); | |
| 139 } | |
| 140 }; | |
| 141 | |
| 142 var parameters = { | |
| 143 'client_id': clientId, | |
| 144 'client_secret': clientSecret, | |
| 145 'redirect_uri': redirectUri, | |
| 146 'code': code, | |
| 147 'grant_type': 'authorization_code' | |
| 148 }; | |
| 149 remoting.xhr.post(this.getOAuth2TokenEndpoint_(), onResponse, parameters); | |
| 150 }; | 51 }; |
| 151 | 52 |
| 152 /** | 53 /** |
| 153 * Get the user's email address. | 54 * Get the user's email address. |
| 154 * | 55 * |
| 56 * TODO(jamiewalch): Reorder these parameters to match the typical chrome API | |
| 57 * convention of having callbacks at the end and remove the token parameter | |
| 58 * to match remoting.HostListApi. | |
|
Jamie
2015/01/08 18:57:18
I don't want to do this just yet because it will m
| |
| 59 * | |
| 155 * @param {function(string):void} onDone Callback invoked when the email | 60 * @param {function(string):void} onDone Callback invoked when the email |
| 156 * address is available. | 61 * address is available. |
| 157 * @param {function(remoting.Error):void} onError Callback invoked if an | 62 * @param {function(remoting.Error):void} onError Callback invoked if an |
| 158 * error occurs. | 63 * error occurs. |
| 159 * @param {string} token Access token. | 64 * @param {string} token Access token. |
| 160 * @return {void} Nothing. | 65 * @return {void} Nothing. |
| 161 */ | 66 */ |
| 162 remoting.OAuth2Api.prototype.getEmail = function(onDone, onError, token) { | 67 remoting.OAuth2Api.prototype.getEmail = function(onDone, onError, token) { |
| 163 /** @param {XMLHttpRequest} xhr */ | |
| 164 var onResponse = function(xhr) { | |
| 165 if (xhr.status == 200) { | |
| 166 try { | |
| 167 var result = JSON.parse(xhr.responseText); | |
| 168 onDone(result['email']); | |
| 169 } catch (err) { | |
| 170 console.error('Invalid "userinfo" response from server:', | |
| 171 /** @type {*} */ (err)); | |
| 172 onError(remoting.Error.UNEXPECTED); | |
| 173 } | |
| 174 } else { | |
| 175 console.error('Failed to get email. Status: ' + xhr.status + | |
| 176 ' response: ' + xhr.responseText); | |
| 177 onError(remoting.Error.fromHttpError(xhr.status)); | |
| 178 } | |
| 179 }; | |
| 180 var headers = { 'Authorization': 'OAuth ' + token }; | |
| 181 remoting.xhr.get(this.getOAuth2ApiUserInfoEndpoint_(), | |
| 182 onResponse, '', headers); | |
| 183 }; | 68 }; |
| 184 | 69 |
| 185 /** | 70 /** |
| 186 * Get the user's email address and full name. | 71 * Get the user's email address and full name. |
| 187 * | 72 * |
| 188 * @param {function(string, string):void} onDone Callback invoked when the email | 73 * @param {function(string, string):void} onDone Callback invoked when the email |
| 189 * address and full name are available. | 74 * address and full name are available. |
| 190 * @param {function(remoting.Error):void} onError Callback invoked if an | 75 * @param {function(remoting.Error):void} onError Callback invoked if an |
| 191 * error occurs. | 76 * error occurs. |
| 192 * @param {string} token Access token. | 77 * @param {string} token Access token. |
| 193 * @return {void} Nothing. | 78 * @return {void} Nothing. |
| 194 */ | 79 */ |
| 195 remoting.OAuth2Api.prototype.getUserInfo = function(onDone, onError, token) { | 80 remoting.OAuth2Api.prototype.getUserInfo = function(onDone, onError, token) { |
| 196 /** @param {XMLHttpRequest} xhr */ | |
| 197 var onResponse = function(xhr) { | |
| 198 if (xhr.status == 200) { | |
| 199 try { | |
| 200 var result = JSON.parse(xhr.responseText); | |
| 201 onDone(result['email'], result['name']); | |
| 202 } catch (err) { | |
| 203 console.error('Invalid "userinfo" response from server:', | |
| 204 /** @type {*} */ (err)); | |
| 205 onError(remoting.Error.UNEXPECTED); | |
| 206 } | |
| 207 } else { | |
| 208 console.error('Failed to get user info. Status: ' + xhr.status + | |
| 209 ' response: ' + xhr.responseText); | |
| 210 onError(remoting.Error.fromHttpError(xhr.status)); | |
| 211 } | |
| 212 }; | |
| 213 var headers = { 'Authorization': 'OAuth ' + token }; | |
| 214 remoting.xhr.get(this.getOAuth2ApiUserInfoEndpoint_(), | |
| 215 onResponse, '', headers); | |
| 216 }; | 81 }; |
| 217 | |
| 218 /** @type {remoting.OAuth2Api} */ | |
| 219 remoting.oauth2Api = new remoting.OAuth2Api(); | |
| OLD | NEW |