Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(400)

Side by Side Diff: remoting/webapp/oauth2.js

Issue 10704240: Cleaned up OAuth refresh error handling. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fixed non-200 response bug. Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 class that handles retrieval/storage of an OAuth2 token. 7 * OAuth2 class that handles retrieval/storage of an OAuth2 token.
8 * 8 *
9 * Uses a content script to trampoline the OAuth redirect page back into the 9 * Uses a content script to trampoline the OAuth redirect page back into the
10 * extension context. This works around the lack of native support for 10 * extension context. This works around the lack of native support for
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
55 return false; 55 return false;
56 }; 56 };
57 57
58 /** 58 /**
59 * Removes all storage, and effectively unauthenticates the user. 59 * Removes all storage, and effectively unauthenticates the user.
60 * 60 *
61 * @return {void} Nothing. 61 * @return {void} Nothing.
62 */ 62 */
63 remoting.OAuth2.prototype.clear = function() { 63 remoting.OAuth2.prototype.clear = function() {
64 window.localStorage.removeItem(this.KEY_EMAIL_); 64 window.localStorage.removeItem(this.KEY_EMAIL_);
65 this.clearAccessToken(); 65 this.clearAccessToken_();
66 this.clearRefreshToken_(); 66 this.clearRefreshToken_();
67 }; 67 };
68 68
69 /** 69 /**
70 * Sets the refresh token. 70 * Sets the refresh token.
71 * 71 *
72 * This method also marks the token as revokable, so that this object will 72 * This method also marks the token as revokable, so that this object will
73 * revoke the token when it no longer needs it. 73 * revoke the token when it no longer needs it.
74 * 74 *
75 * @param {string} token The new refresh token. 75 * @param {string} token The new refresh token.
76 * @return {void} Nothing. 76 * @return {void} Nothing.
77 */ 77 */
78 remoting.OAuth2.prototype.setRefreshToken = function(token) { 78 remoting.OAuth2.prototype.setRefreshToken = function(token) {
79 window.localStorage.setItem(this.KEY_REFRESH_TOKEN_, escape(token)); 79 window.localStorage.setItem(this.KEY_REFRESH_TOKEN_, escape(token));
80 window.localStorage.setItem(this.KEY_REFRESH_TOKEN_REVOKABLE_, true); 80 window.localStorage.setItem(this.KEY_REFRESH_TOKEN_REVOKABLE_, true);
81 this.clearAccessToken(); 81 this.clearAccessToken_();
82 }; 82 };
83 83
84 /** 84 /**
85 * Gets the refresh token. 85 * Gets the refresh token.
86 * 86 *
87 * This method also marks the refresh token as not revokable, so that this 87 * This method also marks the refresh token as not revokable, so that this
88 * object will not revoke the token when it no longer needs it. After this 88 * object will not revoke the token when it no longer needs it. After this
89 * object has exported the token, it cannot know whether it is still in use 89 * object has exported the token, it cannot know whether it is still in use
90 * when this object no longer needs it. 90 * when this object no longer needs it.
91 * 91 *
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
156 console.log('Invalid access token stored.'); 156 console.log('Invalid access token stored.');
157 return {'token': '', 'expiration': 0}; 157 return {'token': '', 'expiration': 0};
158 }; 158 };
159 159
160 /** 160 /**
161 * Returns true if the access token is expired, or otherwise invalid. 161 * Returns true if the access token is expired, or otherwise invalid.
162 * 162 *
163 * Will throw if !isAuthenticated(). 163 * Will throw if !isAuthenticated().
164 * 164 *
165 * @return {boolean} True if a new access token is needed. 165 * @return {boolean} True if a new access token is needed.
166 * @private
166 */ 167 */
167 remoting.OAuth2.prototype.needsNewAccessToken = function() { 168 remoting.OAuth2.prototype.needsNewAccessToken_ = function() {
168 if (!this.isAuthenticated()) { 169 if (!this.isAuthenticated()) {
169 throw 'Not Authenticated.'; 170 throw 'Not Authenticated.';
170 } 171 }
171 var access_token = this.getAccessTokenInternal_(); 172 var access_token = this.getAccessTokenInternal_();
172 if (!access_token['token']) { 173 if (!access_token['token']) {
173 return true; 174 return true;
174 } 175 }
175 if (Date.now() > access_token['expiration']) { 176 if (Date.now() > access_token['expiration']) {
176 return true; 177 return true;
177 } 178 }
178 return false; 179 return false;
179 }; 180 };
180 181
181 /** 182 /**
182 * Returns the current access token. 183 * @return {void} Nothing.
183 *
184 * Will throw if !isAuthenticated() or needsNewAccessToken().
185 *
186 * @return {string} The access token.
187 * @private 184 * @private
188 */ 185 */
189 remoting.OAuth2.prototype.getAccessToken_ = function() { 186 remoting.OAuth2.prototype.clearAccessToken_ = function() {
190 if (this.needsNewAccessToken()) {
191 throw 'Access Token expired.';
192 }
193 return this.getAccessTokenInternal_()['token'];
194 };
195
196 /** @return {void} Nothing. */
197 remoting.OAuth2.prototype.clearAccessToken = function() {
198 window.localStorage.removeItem(this.KEY_ACCESS_TOKEN_); 187 window.localStorage.removeItem(this.KEY_ACCESS_TOKEN_);
199 }; 188 };
200 189
201 /** 190 /**
202 * Update state based on token response from the OAuth2 /token endpoint. 191 * Update state based on token response from the OAuth2 /token endpoint.
203 * 192 *
204 * @private 193 * @private
205 * @param {function(XMLHttpRequest): void} onDone Callback to invoke on 194 * @param {function(XMLHttpRequest, string): void} onDone Callback to invoke on
simonmorris 2012/07/17 01:25:33 Doesn't "string" need to be something like "string
Jamie 2012/07/17 01:51:58 I've changed the default to '', since that minimiz
206 * completion. 195 * completion.
207 * @param {XMLHttpRequest} xhr The XHR object for this request. 196 * @param {XMLHttpRequest} xhr The XHR object for this request.
208 * @return {void} Nothing. 197 * @return {void} Nothing.
209 */ 198 */
210 remoting.OAuth2.prototype.processTokenResponse_ = function(onDone, xhr) { 199 remoting.OAuth2.prototype.processTokenResponse_ = function(onDone, xhr) {
200 var accessToken = null;
211 if (xhr.status == 200) { 201 if (xhr.status == 200) {
212 try { 202 try {
213 // Don't use jsonParseSafe here unless you move the definition out of 203 // Don't use jsonParseSafe here unless you move the definition out of
214 // remoting.js, otherwise this won't work from the OAuth trampoline. 204 // remoting.js, otherwise this won't work from the OAuth trampoline.
215 // TODO(jamiewalch): Fix this once we're no longer using the trampoline. 205 // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
216 var tokens = JSON.parse(xhr.responseText); 206 var tokens = JSON.parse(xhr.responseText);
217 if ('refresh_token' in tokens) { 207 if ('refresh_token' in tokens) {
218 this.setRefreshToken(tokens['refresh_token']); 208 this.setRefreshToken(tokens['refresh_token']);
219 } 209 }
220 210
221 // Offset by 120 seconds so that we can guarantee that the token 211 // Offset by 120 seconds so that we can guarantee that the token
222 // we return will be valid for at least 2 minutes. 212 // we return will be valid for at least 2 minutes.
223 // If the access token is to be useful, this object must make some 213 // If the access token is to be useful, this object must make some
224 // guarantee as to how long the token will be valid for. 214 // guarantee as to how long the token will be valid for.
225 // The choice of 2 minutes is arbitrary, but that length of time 215 // The choice of 2 minutes is arbitrary, but that length of time
226 // is part of the contract satisfied by callWithToken(). 216 // is part of the contract satisfied by callWithToken().
227 // Offset by a further 30 seconds to account for RTT issues. 217 // Offset by a further 30 seconds to account for RTT issues.
228 this.setAccessToken(tokens['access_token'], 218 accessToken = tokens['access_token'];
219 this.setAccessToken(accessToken,
229 (tokens['expires_in'] - (120 + 30)) * 1000 + Date.now()); 220 (tokens['expires_in'] - (120 + 30)) * 1000 + Date.now());
230 } catch (err) { 221 } catch (err) {
231 console.error('Invalid "token" response from server:', 222 console.error('Invalid "token" response from server:',
232 /** @type {*} */ (err)); 223 /** @type {*} */ (err));
233 } 224 }
234 } else { 225 } else {
235 console.error('Failed to get tokens. Status: ' + xhr.status + 226 console.error('Failed to get tokens. Status: ' + xhr.status +
236 ' response: ' + xhr.responseText); 227 ' response: ' + xhr.responseText);
237 } 228 }
238 onDone(xhr); 229 onDone(xhr, accessToken);
239 }; 230 };
240 231
241 /** 232 /**
242 * Asynchronously retrieves a new access token from the server. 233 * Asynchronously retrieves a new access token from the server.
243 * 234 *
244 * Will throw if !isAuthenticated(). 235 * Will throw if !isAuthenticated().
245 * 236 *
246 * @param {function(XMLHttpRequest): void} onDone Callback to invoke on 237 * @param {function(XMLHttpRequest): void} onDone Callback to invoke on
247 * completion. 238 * completion.
248 * @return {void} Nothing. 239 * @return {void} Nothing.
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
323 console.log('Failed to revoke token. Status: ' + xhr.status + 314 console.log('Failed to revoke token. Status: ' + xhr.status +
324 ' ; response: ' + xhr.responseText + ' ; xhr: ', xhr); 315 ' ; response: ' + xhr.responseText + ' ; xhr: ', xhr);
325 } 316 }
326 }; 317 };
327 remoting.xhr.post(this.OAUTH2_REVOKE_TOKEN_ENDPOINT_, 318 remoting.xhr.post(this.OAUTH2_REVOKE_TOKEN_ENDPOINT_,
328 processResponse, 319 processResponse,
329 parameters); 320 parameters);
330 }; 321 };
331 322
332 /** 323 /**
333 * Call myfunc with an access token as the only parameter. 324 * Call a function with an access token, refreshing it first if necessary.
334 *
335 * This will refresh the access token if necessary. If the access token
336 * cannot be refreshed, an error is thrown.
337 *
338 * The access token will remain valid for at least 2 minutes. 325 * The access token will remain valid for at least 2 minutes.
339 * 326 *
340 * @param {function(string):void} onOk Function to invoke with access token if 327 * @param {function(string):void} onOk Function to invoke with access token if
341 * an access token was successfully retrieved. 328 * an access token was successfully retrieved.
342 * @param {function(remoting.Error):void} onError Function to invoke with an 329 * @param {function(remoting.Error):void} onError Function to invoke with an
343 * error code on failure. 330 * error code on failure.
344 * @return {void} Nothing. 331 * @return {void} Nothing.
345 */ 332 */
346 remoting.OAuth2.prototype.callWithToken = function(onOk, onError) { 333 remoting.OAuth2.prototype.callWithToken = function(onOk, onError) {
347 try { 334 if (this.isAuthenticated()) {
348 if (this.needsNewAccessToken()) { 335 if (this.needsNewAccessToken_()) {
349 this.refreshAccessToken_(this.onRefreshToken_.bind(this, onOk, onError)); 336 this.refreshAccessToken_(this.onRefreshToken_.bind(this, onOk, onError));
350 } else { 337 } else {
351 onOk(this.getAccessToken_()); 338 onOk(this.getAccessTokenInternal_()['token']);
352 } 339 }
353 } catch (error) { 340 } else {
354 onError(remoting.Error.NOT_AUTHENTICATED); 341 onError(remoting.Error.NOT_AUTHENTICATED);
355 } 342 }
356 }; 343 };
357 344
358 /** 345 /**
359 * Process token refresh results and notify caller. 346 * Process token refresh results and notify caller.
360 * 347 *
361 * @param {function(string):void} onOk Function to invoke with access token if 348 * @param {function(string):void} onOk Function to invoke with access token if
362 * an access token was successfully retrieved. 349 * an access token was successfully retrieved.
363 * @param {function(remoting.Error):void} onError Function to invoke with an 350 * @param {function(remoting.Error):void} onError Function to invoke with an
364 * error code on failure. 351 * error code on failure.
365 * @param {XMLHttpRequest} xhr The result of the refresh operation. 352 * @param {XMLHttpRequest} xhr The result of the refresh operation.
353 * @param {string} accessToken The fresh access token.
366 * @private 354 * @private
367 */ 355 */
368 remoting.OAuth2.prototype.onRefreshToken_ = function(onOk, onError, xhr) { 356 remoting.OAuth2.prototype.onRefreshToken_ = function(onOk, onError, xhr,
357 accessToken) {
369 var error = remoting.Error.UNEXPECTED; 358 var error = remoting.Error.UNEXPECTED;
370 if (xhr.status == 200) { 359 if (xhr.status == 200) {
371 onOk(this.getAccessToken_()); 360 onOk(accessToken);
372 return; 361 return;
373 } else if (xhr.status == 400) { 362 } else if (xhr.status == 400) {
374 var result = 363 var result =
375 /** @type {{error: string}} */ (jsonParseSafe(xhr.responseText)); 364 /** @type {{error: string}} */ (jsonParseSafe(xhr.responseText));
376 if (result && result.error == 'invalid_grant') { 365 if (result && result.error == 'invalid_grant') {
377 error = remoting.Error.AUTHENTICATION_FAILED; 366 error = remoting.Error.AUTHENTICATION_FAILED;
378 } 367 }
379 } else if (xhr.status == 401) { 368 } else if (xhr.status == 401) {
380 // According to the OAuth2 draft RFC, the server shouldn't return 401, 369 // According to the OAuth2 draft RFC, the server shouldn't return 401,
381 // but AUTHENTICATION_FAILED is the obvious interpretation if it does. 370 // but AUTHENTICATION_FAILED is the obvious interpretation if it does.
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
433 * @return {?string} The email address, if it has been cached by a previous call 422 * @return {?string} The email address, if it has been cached by a previous call
434 * to getEmail, otherwise null. 423 * to getEmail, otherwise null.
435 */ 424 */
436 remoting.OAuth2.prototype.getCachedEmail = function() { 425 remoting.OAuth2.prototype.getCachedEmail = function() {
437 var value = window.localStorage.getItem(this.KEY_EMAIL_); 426 var value = window.localStorage.getItem(this.KEY_EMAIL_);
438 if (typeof value == 'string') { 427 if (typeof value == 'string') {
439 return value; 428 return value;
440 } 429 }
441 return null; 430 return null;
442 }; 431 };
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698