Bug 760910: Handle 401 responses correctly in AITC; r=gps

This commit is contained in:
Nick Desaulniers 2012-07-05 09:32:07 -07:00
parent 05b2273f64
commit aced0a97c4
4 changed files with 127 additions and 25 deletions

View File

@ -376,7 +376,7 @@ AitcClient.prototype = {
// Set values from X-Backoff and Retry-After headers, if present.
_setBackoff: function _setBackoff(req) {
let backoff = 0;
let successfulStatusCodes = [200, 201, 204, 304];
let successfulStatusCodes = [200, 201, 204, 304, 401];
let val;
if (req.response.headers["Retry-After"]) {
@ -387,7 +387,7 @@ AitcClient.prototype = {
val = req.response.headers["X-Backoff"];
backoff = parseInt(val, 10);
this._log.warn("X-Backoff header was seen: " + val);
} else if (successfulStatusCodes.indexOf(req.response.status) === -1) {
} else if (statusCodesWithoutBackoff.indexOf(req.response.status) === -1) {
// Bad status code.
this._consecutiveFailures++;
if (this._consecutiveFailures === this._maxFailures) {

View File

@ -21,7 +21,7 @@ Cu.import("resource://services-common/tokenserverclient.js");
Cu.import("resource://services-common/utils.js");
const PREFS = new Preferences("services.aitc.");
const TOKEN_TIMEOUT = 240000; // 4 minutes
const INITIAL_TOKEN_DURATION = 240000; // 4 minutes
const DASHBOARD_URL = PREFS.get("dashboard.url");
const MARKETPLACE_URL = PREFS.get("marketplace.url");
@ -29,16 +29,20 @@ const MARKETPLACE_URL = PREFS.get("marketplace.url");
* The constructor for the manager takes a callback, which will be invoked when
* the manager is ready (construction is asynchronous). *DO NOT* call any
* methods on this object until the callback has been invoked, doing so will
* lead to undefined behaviour. The premadeClient is used
* to bypass BrowserID for xpcshell tests, since the window object is not
* lead to undefined behaviour. The premadeClient and premadeToken are used
* to bypass BrowserID for xpcshell tests, since the window object in not
* available.
*/
function AitcManager(cb, premadeClient) {
function AitcManager(cb, premadeClient, premadeToken) {
this._client = null;
this._getTimer = null;
this._putTimer = null;
this._lastToken = 0;
this._lastTokenTime = 0;
this._tokenDuration = INITIAL_TOKEN_DURATION;
this._premadeToken = premadeToken || null;
this._invalidTokenFlag = false;
this._lastEmail = null;
this._dashboardWindow = null;
@ -225,11 +229,14 @@ AitcManager.prototype = {
* not be called and an error will be logged.
*/
_validateToken: function _validateToken(func) {
if (Date.now() - this._lastToken < TOKEN_TIMEOUT) {
let timeSinceLastToken = Date.now() - this._lastTokenTime;
if (!this._invalidTokenFlag && timeSinceLastToken < this._tokenDuration) {
this._log.info("Current token is valid");
func();
return;
}
this._log.info("Current token is invalid");
let win;
if (this._state == this.ACTIVE) {
win = this._dashboardWindow;
@ -238,10 +245,10 @@ AitcManager.prototype = {
let self = this;
this._refreshToken(function(err, done) {
if (!done) {
this._log.warn("_checkServer could not refresh token, aborting");
self._log.warn("_checkServer could not refresh token, aborting");
return;
}
func();
func(err);
}, win);
},
@ -260,7 +267,14 @@ AitcManager.prototype = {
return;
}
this._validateToken(this._getApps.bind(this));
let self = this;
this._validateToken(function validation(err) {
if (err) {
self._log.error(err);
} else {
self._getApps();
}
});
},
_getApps: function _getApps() {
@ -271,7 +285,16 @@ AitcManager.prototype = {
this._client.getApps(function gotApps(err, apps) {
if (err) {
// Error was logged in client.
return;
if (err.authfailure) {
self._invalidTokenFlag = true;
self._validateToken(function revalidated(err) {
if (!err) {
self._getApps();
}
});
} else {
return;
}
}
if (!apps) {
// No changes, got 304.
@ -310,7 +333,14 @@ AitcManager.prototype = {
return;
}
this._validateToken(this._putApps.bind(this));
let self = this;
this._validateToken(function validation(err) {
if (err) {
self._log.error(err);
} else {
self._putApps();
}
});
},
_putApps: function _putApps() {
@ -324,6 +354,17 @@ AitcManager.prototype = {
// Send to end of queue if unsuccessful or err.removeFromQueue is false.
if (err && !err.removeFromQueue) {
self._log.info("PUT failed, re-adding to queue");
// Error was logged in client.
if (err.authfailure) {
self._invalidTokenFlag = true;
self._validateToken(function validation(err) {
if (err) {
self._log.error("Failed to obtain an updated token");
}
_reschedule();
});
return;
}
// Update retries and time
record.retries += 1;
@ -427,8 +468,9 @@ AitcManager.prototype = {
cb(err, null);
return;
}
self._lastToken = Date.now();
self._lastTokenTime = Date.now();
self._client.updateToken(token);
self._invalidTokenFlag = false;
cb(null, true);
});
return;
@ -449,6 +491,7 @@ AitcManager.prototype = {
// makeClient sets an updated token.
self._client = client;
self._invalidTokenFlag = false;
cb(null, true);
}, win);
}
@ -459,7 +502,15 @@ AitcManager.prototype = {
} else {
options.sameEmailAs = MARKETPLACE_URL;
}
BrowserID.getAssertion(refreshedAssertion, options);
if (this._premadeToken) {
this._client.updateToken(this._premadeToken);
this._tokenDuration = parseInt(this._premadeToken.duration, 10);
this._lastTokenTime = Date.now();
this._invalidTokenFlag = false;
cb(null, true);
} else {
BrowserID.getAssertion(refreshedAssertion, options);
}
},
/* Obtain a token from Sagrada token server, given a BrowserID assertion
@ -485,6 +536,7 @@ AitcManager.prototype = {
_gotToken: function _gotToken(err, tok, cb) {
if (!err) {
this._log.info("Got token from server: " + JSON.stringify(tok));
this._tokenDuration = parseInt(tok.duration, 10);
cb(null, tok);
return;
}
@ -539,7 +591,7 @@ AitcManager.prototype = {
}
// Store when we got the token so we can refresh it as needed.
self._lastToken = Date.now();
self._lastTokenTime = Date.now();
// We only create one client instance, store values in a pref tree
cb(null, new AitcClient(

View File

@ -72,15 +72,62 @@ function get_client_for_server(username, server) {
return new AitcClient(token, new Preferences("services.aitc.client."));
}
// Check that a is less than b
// Check that a is less than b.
function do_check_lt(a, b) {
do_check_true(a < b);
}
add_test(function test_401_responses() {
PREFS.set("client.backoff", "50");
PREFS.set("manager.putFreq", 50);
const app = get_mock_app();
const username = "123";
const premadeToken = {
id: "testtest",
key: "testtest",
endpoint: "http://localhost:8080/1.0/123",
uid: "uid",
duration: "5000"
};
let server = get_server_with_user(username);
server.mockStatus = {
code: 401,
method: "Unauthorized"
}
let client = get_client_for_server(username, server);
let manager = new AitcManager(function () {}, client, premadeToken);
// Assume first token is not out dated.
manager._lastTokenTime = Date.now();
let mockRequestCount = 0;
let clientFirstToken = null;
server.onRequest = function mockstatus () {
mockRequestCount++;
switch (mockRequestCount) {
case 1:
clientFirstToken = client.token;
// Switch to using mock 201s.
this.mockStatus = {
code: 201,
method: "Created"
};
break;
case 2:
// Check that the client obtained a different token.
do_check_neq(client.token.id, clientFirstToken.id);
do_check_neq(client.token.key, clientFirstToken.key);
server.stop(run_next_test);
break;
}
}
manager.appEvent("install", get_mock_app());
});
add_test(function test_client_exponential_backoff() {
_("Test that the client is properly setting the backoff");
// Use prefs to speed up tests
// Use prefs to speed up tests.
const putFreq = 50;
const initialBackoff = 50;
const username = "123";
@ -90,7 +137,7 @@ add_test(function test_client_exponential_backoff() {
let mockRequestCount = 0;
let lastRequestTime = Date.now();
// Create server that returns failure codes
// Create server that returns failure codes.
let server = get_server_with_user(username);
server.mockStatus = {
code: 399,
@ -106,26 +153,26 @@ add_test(function test_client_exponential_backoff() {
lastRequestTime = timeNow;
// The time between the 3rd and 4th request should be atleast the
// initial backoff
// initial backoff.
if (mockRequestCount === 4) {
do_check_lt(initialBackoff, timeDiff);
// The time beween the 4th and 5th request should be atleast double
// the intial backoff
// the intial backoff.
} else if (mockRequestCount === 5) {
do_check_lt(initialBackoff * 2, timeDiff);
server.stop(run_next_test);
}
}
// Create dummy client and manager
// Create dummy client and manager.
let client = get_client_for_server(username, server);
let manager = new AitcManager(function() {
CommonUtils.nextTick(gotManager);
}, client);
function gotManager() {
manager._lastToken = new Date();
// Create a bunch of dummy apps for the queue to cycle through
manager._lastTokenTime = Date.now();
// Create a bunch of dummy apps for the queue to cycle through.
manager._pending._queue = [
get_mock_queue_element(),
get_mock_queue_element(),

View File

@ -114,6 +114,7 @@ TokenServerClient.prototype = {
* key (string) HTTP MAC shared symmetric key.
* endpoint (string) URL where service can be connected to.
* uid (string) user ID for requested service.
* duration (string) the validity duration of the issued token.
*
* e.g.
*
@ -128,7 +129,9 @@ TokenServerClient.prototype = {
* return;
* }
*
* let {id: id, key: key, uid: uid, endpoint: endpoint} = result;
* let {
* id: id, key: key, uid: uid, endpoint: endpoint, duration: duration
* } = result;
* // Do stuff with data and carry on.
* });
*