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

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 JSCompiler errors. 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
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 /** @type {string} */
201 var accessToken = '';
211 if (xhr.status == 200) { 202 if (xhr.status == 200) {
212 try { 203 try {
213 // Don't use jsonParseSafe here unless you move the definition out of 204 // Don't use jsonParseSafe here unless you move the definition out of
214 // remoting.js, otherwise this won't work from the OAuth trampoline. 205 // remoting.js, otherwise this won't work from the OAuth trampoline.
215 // TODO(jamiewalch): Fix this once we're no longer using the trampoline. 206 // TODO(jamiewalch): Fix this once we're no longer using the trampoline.
216 var tokens = JSON.parse(xhr.responseText); 207 var tokens = JSON.parse(xhr.responseText);
217 if ('refresh_token' in tokens) { 208 if ('refresh_token' in tokens) {
218 this.setRefreshToken(tokens['refresh_token']); 209 this.setRefreshToken(tokens['refresh_token']);
219 } 210 }
220 211
221 // Offset by 120 seconds so that we can guarantee that the token 212 // Offset by 120 seconds so that we can guarantee that the token
222 // we return will be valid for at least 2 minutes. 213 // we return will be valid for at least 2 minutes.
223 // If the access token is to be useful, this object must make some 214 // 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. 215 // guarantee as to how long the token will be valid for.
225 // The choice of 2 minutes is arbitrary, but that length of time 216 // The choice of 2 minutes is arbitrary, but that length of time
226 // is part of the contract satisfied by callWithToken(). 217 // is part of the contract satisfied by callWithToken().
227 // Offset by a further 30 seconds to account for RTT issues. 218 // Offset by a further 30 seconds to account for RTT issues.
228 this.setAccessToken(tokens['access_token'], 219 accessToken = /** @type {string} */ (tokens['access_token']);
220 this.setAccessToken(accessToken,
229 (tokens['expires_in'] - (120 + 30)) * 1000 + Date.now()); 221 (tokens['expires_in'] - (120 + 30)) * 1000 + Date.now());
230 } catch (err) { 222 } catch (err) {
231 console.error('Invalid "token" response from server:', 223 console.error('Invalid "token" response from server:',
232 /** @type {*} */ (err)); 224 /** @type {*} */ (err));
233 } 225 }
234 } else { 226 } else {
235 console.error('Failed to get tokens. Status: ' + xhr.status + 227 console.error('Failed to get tokens. Status: ' + xhr.status +
236 ' response: ' + xhr.responseText); 228 ' response: ' + xhr.responseText);
237 } 229 }
238 onDone(xhr); 230 onDone(xhr, accessToken);
239 }; 231 };
240 232
241 /** 233 /**
242 * Asynchronously retrieves a new access token from the server. 234 * Asynchronously retrieves a new access token from the server.
243 * 235 *
244 * Will throw if !isAuthenticated(). 236 * Will throw if !isAuthenticated().
245 * 237 *
246 * @param {function(XMLHttpRequest): void} onDone Callback to invoke on 238 * @param {function(XMLHttpRequest): void} onDone Callback to invoke on
247 * completion. 239 * completion.
248 * @return {void} Nothing. 240 * @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 + 315 console.log('Failed to revoke token. Status: ' + xhr.status +
324 ' ; response: ' + xhr.responseText + ' ; xhr: ', xhr); 316 ' ; response: ' + xhr.responseText + ' ; xhr: ', xhr);
325 } 317 }
326 }; 318 };
327 remoting.xhr.post(this.OAUTH2_REVOKE_TOKEN_ENDPOINT_, 319 remoting.xhr.post(this.OAUTH2_REVOKE_TOKEN_ENDPOINT_,
328 processResponse, 320 processResponse,
329 parameters); 321 parameters);
330 }; 322 };
331 323
332 /** 324 /**
333 * Call myfunc with an access token as the only parameter. 325 * 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. 326 * The access token will remain valid for at least 2 minutes.
339 * 327 *
340 * @param {function(string):void} onOk Function to invoke with access token if 328 * @param {function(string):void} onOk Function to invoke with access token if
341 * an access token was successfully retrieved. 329 * an access token was successfully retrieved.
342 * @param {function(remoting.Error):void} onError Function to invoke with an 330 * @param {function(remoting.Error):void} onError Function to invoke with an
343 * error code on failure. 331 * error code on failure.
344 * @return {void} Nothing. 332 * @return {void} Nothing.
345 */ 333 */
346 remoting.OAuth2.prototype.callWithToken = function(onOk, onError) { 334 remoting.OAuth2.prototype.callWithToken = function(onOk, onError) {
347 try { 335 if (this.isAuthenticated()) {
348 if (this.needsNewAccessToken()) { 336 if (this.needsNewAccessToken_()) {
349 this.refreshAccessToken_(this.onRefreshToken_.bind(this, onOk, onError)); 337 this.refreshAccessToken_(this.onRefreshToken_.bind(this, onOk, onError));
350 } else { 338 } else {
351 onOk(this.getAccessToken_()); 339 onOk(this.getAccessTokenInternal_()['token']);
352 } 340 }
353 } catch (error) { 341 } else {
354 onError(remoting.Error.NOT_AUTHENTICATED); 342 onError(remoting.Error.NOT_AUTHENTICATED);
355 } 343 }
356 }; 344 };
357 345
358 /** 346 /**
359 * Process token refresh results and notify caller. 347 * Process token refresh results and notify caller.
360 * 348 *
361 * @param {function(string):void} onOk Function to invoke with access token if 349 * @param {function(string):void} onOk Function to invoke with access token if
362 * an access token was successfully retrieved. 350 * an access token was successfully retrieved.
363 * @param {function(remoting.Error):void} onError Function to invoke with an 351 * @param {function(remoting.Error):void} onError Function to invoke with an
364 * error code on failure. 352 * error code on failure.
365 * @param {XMLHttpRequest} xhr The result of the refresh operation. 353 * @param {XMLHttpRequest} xhr The result of the refresh operation.
354 * @param {string} accessToken The fresh access token.
366 * @private 355 * @private
367 */ 356 */
368 remoting.OAuth2.prototype.onRefreshToken_ = function(onOk, onError, xhr) { 357 remoting.OAuth2.prototype.onRefreshToken_ = function(onOk, onError, xhr,
358 accessToken) {
369 var error = remoting.Error.UNEXPECTED; 359 var error = remoting.Error.UNEXPECTED;
370 if (xhr.status == 200) { 360 if (xhr.status == 200) {
371 onOk(this.getAccessToken_()); 361 onOk(accessToken);
372 return; 362 return;
373 } else if (xhr.status == 400) { 363 } else if (xhr.status == 400) {
374 var result = 364 var result =
375 /** @type {{error: string}} */ (jsonParseSafe(xhr.responseText)); 365 /** @type {{error: string}} */ (jsonParseSafe(xhr.responseText));
376 if (result && result.error == 'invalid_grant') { 366 if (result && result.error == 'invalid_grant') {
377 error = remoting.Error.AUTHENTICATION_FAILED; 367 error = remoting.Error.AUTHENTICATION_FAILED;
378 } 368 }
379 } else if (xhr.status == 401) { 369 } else if (xhr.status == 401) {
380 // According to the OAuth2 draft RFC, the server shouldn't return 401, 370 // According to the OAuth2 draft RFC, the server shouldn't return 401,
381 // but AUTHENTICATION_FAILED is the obvious interpretation if it does. 371 // 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 423 * @return {?string} The email address, if it has been cached by a previous call
434 * to getEmail, otherwise null. 424 * to getEmail, otherwise null.
435 */ 425 */
436 remoting.OAuth2.prototype.getCachedEmail = function() { 426 remoting.OAuth2.prototype.getCachedEmail = function() {
437 var value = window.localStorage.getItem(this.KEY_EMAIL_); 427 var value = window.localStorage.getItem(this.KEY_EMAIL_);
438 if (typeof value == 'string') { 428 if (typeof value == 'string') {
439 return value; 429 return value;
440 } 430 }
441 return null; 431 return null;
442 }; 432 };
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