Skip to content
This repository has been archived by the owner on May 10, 2019. It is now read-only.

Commit

Permalink
(loadgen) complete add_email activity - issue #504
Browse files Browse the repository at this point in the history
  • Loading branch information
lloyd committed Nov 22, 2011
1 parent 37a2293 commit c02f237
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 127 deletions.
7 changes: 7 additions & 0 deletions bin/load_gen
Expand Up @@ -40,6 +40,9 @@
* tool, which is capable of analysing the maximum active users that
* a browserid deployment can support */

const winston = require('winston');


// option processing with optimist
var argv = require('optimist')
.usage('Apply load to a BrowserID server.\nUsage: $0', [ "foo" ])
Expand All @@ -64,6 +67,7 @@ if (args.h) {
process.exit(1);
}


// global configuration
const configuration = {
verifier: args.v ? args.v : args.s + "/verify",
Expand Down Expand Up @@ -103,10 +107,12 @@ var activity = {
// users forget their password once every 4 weeks
probability: (1.0 / (40 * 28.0))
},
*/
"add_email": {
// users add a new email address once every 2 weeks
probability: (1.0 / (40 * 14.0))
},
/*
"reauth": {
// users must re-authenticate to browser id once a week
// (once every two weeks per device)
Expand Down Expand Up @@ -182,6 +188,7 @@ function poll() {
if (!args.o || act !== 'include_only') {
outstanding++;
activity[act].startFunc(configuration, function(err) {
if (err) winston.error(err);
outstanding--;
if (undefined === completed[act]) completed[act] = [ 0, 0 ];
completed[act][err ? 1 : 0]++;
Expand Down
107 changes: 55 additions & 52 deletions lib/load_gen/activities/add_email.js
Expand Up @@ -38,49 +38,29 @@
* user with an active session adding a new email with browserid. */

const
wcli = require("../../libs/wsapi_client.js"),
userdb = require("./user_db.js"),
winston = require('winston');

function authenticated(cfg, context, email, password, cb) {
wcli.get(cfg, '/wsapi/am_authed', context, {}, function(r) {
if (r.body === 'true') cb();
else {
wcli.post(
cfg, '/wsapi/authenticate_user', context,
{ email: email, pass: password },
function(r) {
if (r.code != 200 || r.body !== "true") {
winston.error('authentication failure: ' + r.code + "/" + r.body);
}
cb();
});
}
});
}
wcli = require("../../wsapi_client.js"),
userdb = require("../user_db.js"),
winston = require('winston'),
prepare = require('../prepare.js');

exports.startFunc = function(cfg, cb) {
// 1. RP includes include.js
// 2. users' browser loads all code associated with dialog
// 3. /wsapi/add_email is called to stage the email address
// 4. in the load testing environment, we make a call to the server to get
// the email verification token
// 5. /prove is called on the server, passing in the authentication token
// 6. /manage is called on the server as the user's page transitions from
// the verify screen to the manage screen.
// 7. /wsapi/sync_emails is called from the client upon verification
// (this is a bug, isn't it?)
// 8. /wsapi/set_key is called from the client to inform the server of the
// user's public key for this new email (XXX: this will go away when we migrate to certificates
// and instead, the server will be asked to sign the user's public key.)
// 9. the RP calls /verify to verify a generated assertion
// 1. RP includes include.js
// 2. session_context is called
// 3. list_emails is called
// 4. stage_email is called
// 5. email_addition_status is invoked some number of times while the dialog polls
// 6. landing page is loaded:
// 6a. session_context
// 6b. complete_email_addition
// 7. email_addition_status returns 'complete'
// 8. a key is generated and added

// first let's get an existing user
var user = userdb.getExistingUser();

if (!user) {
winston.warn("can't achieve desired concurrency! not enough users!");
return cb(false);
return cb("not enough users");
}

// user will be "released" once we're done with her.
Expand All @@ -98,44 +78,67 @@ exports.startFunc = function(cfg, cb) {
// pick one of the user's emails that we'll use
var email = userdb.addEmailToUser(user);

var keypair = userdb.addKeyToUserCtx(context, email);
var origin = userdb.any(user.sites);

authenticated(cfg, context, user.emails[0], user.password, function() {
prepare.auth(cfg, user, context, user.emails[0], function(err) {
if (err) return cb(err);
// stage them
wcli.post(cfg, '/wsapi/add_email', context, {
wcli.post(cfg, '/wsapi/stage_email', context, {
email: email,
pubkey: keypair.pub,
site: userdb.any(user.sites)
}, function (r) {
if (r.code !== 200) {
winston.error('failed to add email: ' + email + ' to existing user ' + user.emails[0]);
console.log(r);
return cb(false);
var msg = 'failed to add email: ' + email + ' to existing user ' +
user.emails[0];
winston.error(msg);
return cb(msg);
}
// now get the verification secret
wcli.get(cfg, '/wsapi/fake_verification', context, {
email: email
}, function (r) {
if (r.code !== 200) {
winston.error('failed to fetch verification token for email: ' + email);
return cb(false);
var err ='failed to fetch verification token for email: ' + email;
winston.error(err);
return cb(err);
}
var token = r.body;

// and simulate clickthrough
wcli.get(cfg, '/wsapi/prove_email_ownership', context, {
wcli.post(cfg, '/wsapi/complete_email_addition', context, {
token: token
}, function (r) {
if (r.code !== 200 || r.body !== 'true') {
winston.error('failed to prove email owndership for: ' + email + ' (' + token + ')');
return cb(false);
try {
if (r.code !== 200) throw "bad response code";
if (JSON.parse(r.body).success !== true) throw "success? no.";
} catch (e) {
var err = 'failed to complete email addition for: ' + email + ' (' + token + '): ' + e.toString();
winston.error(err);
process.exit(1);
return cb(err);
}

// and now we should call registration status to complete the
// process
wcli.get(cfg, '/wsapi/registration_status', context, {
wcli.get(cfg, '/wsapi/email_addition_status', context, {
email: email
}, function(r) {
var rv = (r.code === 200 && r.body === '"complete"');
if (!rv) winston.error("registration_status failed during signup: " + JSON.stringify(r));
cb(rv);
try {
if (r.code !== 200) throw "bad response code";
if (JSON.parse(r.body).status !== 'complete') throw "addition not complete? wrong: " + r.body;
} catch(e) {
var err = "registration_status failed during signup: " + e.toString();
winston.error(err);
return cb(err);
}

// now generate a key
prepare.authAndKey(cfg, user, context, email, function(err) {
if (err) return cb(err);
prepare.genAssertionAndVerify(cfg, user, context, email, origin, function(err) {
cb(err);
});
});
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion lib/load_gen/activities/reauth.js
Expand Up @@ -89,7 +89,7 @@ function syncEmails(cfg, context, cb) {
}

exports.startFunc = function(cfg, cb) {
// 1. RP includes include.js
// 1. RP includes include.js
// 2. users' browser loads all code associated with dialog
// 3. in page javascript calls CSRF to get a CSRF token
// 4. /wsapi/authenticate_user is called once the user enters credentials
Expand Down
41 changes: 6 additions & 35 deletions lib/load_gen/activities/signin.js
Expand Up @@ -58,7 +58,7 @@ exports.startFunc = function(cfg, cb) {

if (!user) {
winston.warn("can't achieve desired concurrency! not enough users!");
return cb(false);
return cb("not enough users");
}

// unlock the user when we're done with them
Expand All @@ -79,39 +79,10 @@ exports.startFunc = function(cfg, cb) {
var origin = userdb.any(user.sites);

// establish session context and authenticate if needed
prepare(cfg, user, context, email, function(err) {
try {
serverTime = new Date(context.session.server_time);
wcli.get(cfg, '/wsapi/list_emails', context, undefined, function (r) {
// just verify that we got a JSON object, we don't care about
// the contents so much
try {
if (!typeof JSON.parse(r.body) === 'object') throw 'bogus response';
} catch(e) {
return cb(false);
}

var assertion = crypto.getAssertion({
now: serverTime,
secretKey: context.keys[email].keyPair.secretKey,
cert: context.keys[email].cert,
audience: origin,
email: email
});

wcli.post(cfg, '/verify', {}, {
audience: origin,
assertion: assertion
}, function (r) {
try {
cb(JSON.parse(r.body).status === 'okay' ? undefined : "verification failed");
} catch(e) {
return cb(e.toString);
}
});
});
} catch(e) {
cb(e.toString());
}
prepare.authAndKey(cfg, user, context, email, function(err) {
if (err) return cb(err);
prepare.genAssertionAndVerify(cfg, user, context, email, origin, function(err) {
cb(err);
});
});
};
8 changes: 4 additions & 4 deletions lib/load_gen/activities/signup.js
Expand Up @@ -96,7 +96,6 @@ exports.startFunc = function(cfg, cb) {
if (r.code !== 200) return cb(false);
// now get the verification secret
wcli.get(cfg, '/wsapi/fake_verification', context, {

email: email
}, function (r) {
if (r.code !== 200) return cb(false);
Expand All @@ -106,11 +105,12 @@ exports.startFunc = function(cfg, cb) {
pass: user.password
}, function (r) {
r.body = JSON.parse(r.body);
if (r.code !== 200 || r.body.success !== true) return cb(false);

if (r.code !== 200 || r.body.success !== true) {
return cb("failed to complete user creation");
}
// and now let's prepare this idenity in this context (get a keypair
// and certify it)
prepare(cfg, user, context, email, function(err) {
prepare.authAndKey(cfg, user, context, email, function(err) {
cb(err);
});
});
Expand Down
90 changes: 58 additions & 32 deletions lib/load_gen/prepare.js
@@ -1,35 +1,30 @@
// the common steps required to "prepare" an identity for use inside
// a simulated browser context live here. This includes:
//
// 1. authenticating if requried
// 2. generating a keypair if required
// 3. certifying that keypair
// 4. storing all this crap on the session.
// some common procedures.

const
wcli = require("../wsapi_client.js"),
userdb = require("./user_db.js");
userdb = require("./user_db.js"),
crypto = require("./crypto.js");

module.exports = function(cfg, user, ctx, email, cb) {
function doAuth(lcb) {
if (ctx.session && ctx.session.authenticated) {
lcb();
} else {
wcli.post(
cfg, '/wsapi/authenticate_user', ctx,
{ email: email, pass: user.password },
function(r) {
if (JSON.parse(r.body).success !== true) return cb("failed to authenticate");
ctx.session.authenticated = true;
lcb();
}
);
}
};
exports.auth = function(cfg, user, ctx, email, cb) {
if (ctx.session && ctx.session.authenticated) {
cb();
} else {
wcli.post(
cfg, '/wsapi/authenticate_user', ctx,
{ email: email, pass: user.password },
function(r) {
if (JSON.parse(r.body).success !== true) return cb("failed to authenticate");
ctx.session.authenticated = true;
cb();
}
);
}
};

function genKey(lcb) {
exports.authAndKey = function(cfg, user, ctx, email, cb) {
function genKey(cb) {
if (ctx.keys && ctx.keys[email]) {
lcb();
cb();
} else {
var keypair = userdb.addKeyToUserCtx(ctx, email);
// and now let's certify the pubkey
Expand All @@ -39,15 +34,46 @@ module.exports = function(cfg, user, ctx, email, cb) {
}, function(resp) {
if (typeof resp.body !== 'string') return cb("can't certify key");
userdb.addCertToUserCtx(ctx, email, resp.body);
lcb();
cb();
});
}
};

doAuth(function() {
genKey(function() {
// all done!
cb();
exports.auth(cfg, user, ctx, email, function(err) {
if (err) return cb(err);
genKey(cb);
});
};

exports.genAssertionAndVerify = function(cfg, user, ctx, email, audience, cb) {
var serverTime = new Date(ctx.session.server_time);
wcli.get(cfg, '/wsapi/list_emails', ctx, undefined, function (r) {
// just verify that we got a JSON object, we don't care about
// the contents so much
try {
if (!typeof JSON.parse(r.body) === 'object') throw 'bogus response';
} catch(e) {
return cb(e.toString());
}

var assertion = crypto.getAssertion({
now: serverTime,
secretKey: ctx.keys[email].keyPair.secretKey,
cert: ctx.keys[email].cert,
audience: audience,
email: email
});

wcli.post(cfg, '/verify', {}, {
audience: audience,
assertion: assertion
}, function (r) {
try {
cb(JSON.parse(r.body).status === 'okay' ? undefined : "verification failed");
} catch(e) {
return cb("response body: " + r.body + " error: " + e.toString());
}
});
});
};
}

0 comments on commit c02f237

Please sign in to comment.