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

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: 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() {
Jamie 2012/07/17 00:25:38 Only used in two places, one of which was a potent
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
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) {
211 if (xhr.status == 200) { 200 if (xhr.status == 200) {
212 try { 201 try {
213 // Don't use jsonParseSafe here unless you move the definition out of 202 // Don't use jsonParseSafe here unless you move the definition out of
214 // remoting.js, otherwise this won't work from the OAuth trampoline. 203 // remoting.js, otherwise this won't work from the OAuth trampoline.
215 // TODO(jamiewalch): Fix this once we're no longer using the trampoline. 204 // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
(...skipping 12 matching lines...) Expand all
228 this.setAccessToken(tokens['access_token'], 217 this.setAccessToken(tokens['access_token'],
229 (tokens['expires_in'] - (120 + 30)) * 1000 + Date.now()); 218 (tokens['expires_in'] - (120 + 30)) * 1000 + Date.now());
230 } catch (err) { 219 } catch (err) {
231 console.error('Invalid "token" response from server:', 220 console.error('Invalid "token" response from server:',
232 /** @type {*} */ (err)); 221 /** @type {*} */ (err));
233 } 222 }
234 } else { 223 } else {
235 console.error('Failed to get tokens. Status: ' + xhr.status + 224 console.error('Failed to get tokens. Status: ' + xhr.status +
236 ' response: ' + xhr.responseText); 225 ' response: ' + xhr.responseText);
237 } 226 }
238 onDone(xhr); 227 onDone(xhr, tokens['access_token']);
simonmorris 2012/07/17 00:52:50 Is 'tokens' in scope here? What happens if xhr.sta
Jamie 2012/07/17 01:14:11 You're right, it will fail. Good catch!
239 }; 228 };
240 229
241 /** 230 /**
242 * Asynchronously retrieves a new access token from the server. 231 * Asynchronously retrieves a new access token from the server.
243 * 232 *
244 * Will throw if !isAuthenticated(). 233 * Will throw if !isAuthenticated().
245 * 234 *
246 * @param {function(XMLHttpRequest): void} onDone Callback to invoke on 235 * @param {function(XMLHttpRequest): void} onDone Callback to invoke on
247 * completion. 236 * completion.
248 * @return {void} Nothing. 237 * @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 + 312 console.log('Failed to revoke token. Status: ' + xhr.status +
324 ' ; response: ' + xhr.responseText + ' ; xhr: ', xhr); 313 ' ; response: ' + xhr.responseText + ' ; xhr: ', xhr);
325 } 314 }
326 }; 315 };
327 remoting.xhr.post(this.OAUTH2_REVOKE_TOKEN_ENDPOINT_, 316 remoting.xhr.post(this.OAUTH2_REVOKE_TOKEN_ENDPOINT_,
328 processResponse, 317 processResponse,
329 parameters); 318 parameters);
330 }; 319 };
331 320
332 /** 321 /**
333 * Call myfunc with an access token as the only parameter. 322 * 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. 323 * The access token will remain valid for at least 2 minutes.
339 * 324 *
340 * @param {function(string):void} onOk Function to invoke with access token if 325 * @param {function(string):void} onOk Function to invoke with access token if
341 * an access token was successfully retrieved. 326 * an access token was successfully retrieved.
342 * @param {function(remoting.Error):void} onError Function to invoke with an 327 * @param {function(remoting.Error):void} onError Function to invoke with an
343 * error code on failure. 328 * error code on failure.
344 * @return {void} Nothing. 329 * @return {void} Nothing.
345 */ 330 */
346 remoting.OAuth2.prototype.callWithToken = function(onOk, onError) { 331 remoting.OAuth2.prototype.callWithToken = function(onOk, onError) {
347 try { 332 if (this.isAuthenticated()) {
348 if (this.needsNewAccessToken()) { 333 if (this.needsNewAccessToken_()) {
349 this.refreshAccessToken_(this.onRefreshToken_.bind(this, onOk, onError)); 334 this.refreshAccessToken_(this.onRefreshToken_.bind(this, onOk, onError));
350 } else { 335 } else {
351 onOk(this.getAccessToken_()); 336 onOk(this.getAccessTokenInternal_()['token']);
352 } 337 }
353 } catch (error) { 338 } else {
354 onError(remoting.Error.NOT_AUTHENTICATED); 339 onError(remoting.Error.NOT_AUTHENTICATED);
355 } 340 }
356 }; 341 };
357 342
358 /** 343 /**
359 * Process token refresh results and notify caller. 344 * Process token refresh results and notify caller.
360 * 345 *
361 * @param {function(string):void} onOk Function to invoke with access token if 346 * @param {function(string):void} onOk Function to invoke with access token if
362 * an access token was successfully retrieved. 347 * an access token was successfully retrieved.
363 * @param {function(remoting.Error):void} onError Function to invoke with an 348 * @param {function(remoting.Error):void} onError Function to invoke with an
364 * error code on failure. 349 * error code on failure.
365 * @param {XMLHttpRequest} xhr The result of the refresh operation. 350 * @param {XMLHttpRequest} xhr The result of the refresh operation.
351 * @param {string} accessToken The fresh access token.
366 * @private 352 * @private
367 */ 353 */
368 remoting.OAuth2.prototype.onRefreshToken_ = function(onOk, onError, xhr) { 354 remoting.OAuth2.prototype.onRefreshToken_ = function(onOk, onError, xhr,
355 accessToken) {
369 var error = remoting.Error.UNEXPECTED; 356 var error = remoting.Error.UNEXPECTED;
370 if (xhr.status == 200) { 357 if (xhr.status == 200) {
371 onOk(this.getAccessToken_()); 358 onOk(accessToken);
372 return; 359 return;
373 } else if (xhr.status == 400) { 360 } else if (xhr.status == 400) {
374 var result = 361 var result =
375 /** @type {{error: string}} */ (jsonParseSafe(xhr.responseText)); 362 /** @type {{error: string}} */ (jsonParseSafe(xhr.responseText));
376 if (result && result.error == 'invalid_grant') { 363 if (result && result.error == 'invalid_grant') {
377 error = remoting.Error.AUTHENTICATION_FAILED; 364 error = remoting.Error.AUTHENTICATION_FAILED;
378 } 365 }
379 } else if (xhr.status == 401) { 366 } else if (xhr.status == 401) {
380 // According to the OAuth2 draft RFC, the server shouldn't return 401, 367 // According to the OAuth2 draft RFC, the server shouldn't return 401,
381 // but AUTHENTICATION_FAILED is the obvious interpretation if it does. 368 // 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 420 * @return {?string} The email address, if it has been cached by a previous call
434 * to getEmail, otherwise null. 421 * to getEmail, otherwise null.
435 */ 422 */
436 remoting.OAuth2.prototype.getCachedEmail = function() { 423 remoting.OAuth2.prototype.getCachedEmail = function() {
437 var value = window.localStorage.getItem(this.KEY_EMAIL_); 424 var value = window.localStorage.getItem(this.KEY_EMAIL_);
438 if (typeof value == 'string') { 425 if (typeof value == 'string') {
439 return value; 426 return value;
440 } 427 }
441 return null; 428 return null;
442 }; 429 };
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