Chromium Code Reviews| OLD | NEW | 
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 }; | 
| OLD | NEW |