Write public-key integration tests

This commit is contained in:
Tankred Hase 2016-06-01 12:28:37 +02:00
parent 5d4e162c01
commit a27e833a8d
2 changed files with 271 additions and 30 deletions

View File

@ -40,13 +40,13 @@ class PublicKey {
* @param {Object} openpgp An instance of OpenPGP.js * @param {Object} openpgp An instance of OpenPGP.js
* @param {Object} mongo An instance of the MongoDB client * @param {Object} mongo An instance of the MongoDB client
* @param {Object} email An instance of the Email Sender * @param {Object} email An instance of the Email Sender
* @param {Object} userid An instance of the UserId service * @param {Object} userId An instance of the UserId service
*/ */
constructor(openpgp, mongo, email, userid) { constructor(openpgp, mongo, email, userId) {
this._openpgp = openpgp; this._openpgp = openpgp;
this._mongo = mongo; this._mongo = mongo;
this._email = email; this._email = email;
this._userid = userid; this._userId = userId;
} }
/** /**
@ -59,39 +59,25 @@ class PublicKey {
*put(options) { *put(options) {
// parse key block // parse key block
let publicKeyArmored = options.publicKeyArmored, primaryEmail = options.primaryEmail, origin = options.origin; let publicKeyArmored = options.publicKeyArmored, primaryEmail = options.primaryEmail, origin = options.origin;
let params = this.parseKey(publicKeyArmored); publicKeyArmored = publicKeyArmored.trim(); // remove whitespace
let params = this._parseKey(publicKeyArmored);
// check for existing verfied key by id or email addresses // check for existing verfied key by id or email addresses
let verified = yield this._userid.getVerfied(params); let verified = yield this._userId.getVerfied(params);
if (verified) { if (verified) {
util.throw(304, 'Key for this user already exists: ' + JSON.stringify(verified)); util.throw(304, 'Key for this user already exists');
}
// delete old/unverified key and user ids with the same key id
yield this.remove({ keyid:params.keyid });
// persist new user ids
let userIds = yield this._userid.batch(params);
// persist new key
let r = yield this._mongo.create({ _id:params.keyid, publicKeyArmored }, DB_TYPE);
if (r.insertedCount !== 1) {
// rollback user ids
yield this.remove({ keyid:params.keyid });
util.throw(500, 'Failed to persist key');
} }
// store key in database
let userIds = yield this._persisKey(publicKeyArmored, params);
// send mails to verify user ids (send only one if primary email is provided) // send mails to verify user ids (send only one if primary email is provided)
let primaryUserId = userIds.find(uid => uid.email === primaryEmail); yield this._sendVerifyEmail(userIds, primaryEmail, origin);
if (primaryUserId) {
userIds = [primaryUserId];
}
for (let userId of userIds) {
yield this._email.send({ template:tpl.verifyKey, userId, origin });
}
} }
/** /**
* Parse an ascii armored pgp key block and get its parameters. * Parse an ascii armored pgp key block and get its parameters.
* @param {String} publicKeyArmored The ascii armored pgp key block * @param {String} publicKeyArmored ascii armored pgp key block
* @return {Object} The key's id and user ids * @return {Object} key's id and user ids
*/ */
parseKey(publicKeyArmored) { _parseKey(publicKeyArmored) {
let keys, userIds = []; let keys, userIds = [];
try { try {
keys = this._openpgp.key.readArmored(publicKeyArmored).keys; keys = this._openpgp.key.readArmored(publicKeyArmored).keys;
@ -109,6 +95,45 @@ class PublicKey {
}; };
} }
/**
* Persist the public key and its user ids in the database.
* @param {String} publicKeyArmored ascii armored pgp key block
* @param {Object} params public key parameters
* @yield {Array} The persisted user id documents
*/
*_persisKey(publicKeyArmored, params) {
// delete old/unverified key and user ids with the same key id
yield this.remove({ keyid:params.keyid });
// persist new user ids
let userIds = yield this._userId.batch(params);
// persist new key
let r = yield this._mongo.create({ _id:params.keyid, publicKeyArmored }, DB_TYPE);
if (r.insertedCount !== 1) {
// rollback user ids
yield this.remove({ keyid:params.keyid });
util.throw(500, 'Failed to persist key');
}
return userIds;
}
/**
* Send verification emails to the public keys user ids for verification.
* If a primary email address is provided only one email will be sent.
* @param {Array} userIds user id documents containg the verification nonces
* @param {string} primaryEmail the public key's primary email address
* @param {Object} origin the server's origin (required for email links)
* @yield {undefined}
*/
*_sendVerifyEmail(userIds, primaryEmail, origin) {
let primaryUserId = userIds.find(uid => uid.email === primaryEmail);
if (primaryUserId) {
userIds = [primaryUserId];
}
for (let userId of userIds) {
yield this._email.send({ template:tpl.verifyKey, userId, origin });
}
}
/** /**
* Fetch a verified public key from the database. Either the key id or the * Fetch a verified public key from the database. Either the key id or the
* email address muss be provided. * email address muss be provided.
@ -118,7 +143,7 @@ class PublicKey {
*/ */
*get(options) { *get(options) {
let keyid = options.keyid, email = options.email; let keyid = options.keyid, email = options.email;
let verified = yield this._userid.getVerfied({ let verified = yield this._userId.getVerfied({
keyid: keyid ? keyid.toUpperCase() : undefined, keyid: keyid ? keyid.toUpperCase() : undefined,
userIds: email ? [{ email:email.toLowerCase() }] : undefined userIds: email ? [{ email:email.toLowerCase() }] : undefined
}); });
@ -140,7 +165,10 @@ class PublicKey {
*/ */
*requestRemove(options) { *requestRemove(options) {
let keyid = options.keyid, email = options.email, origin = options.origin; let keyid = options.keyid, email = options.email, origin = options.origin;
let userIds = yield this._userid.flagForRemove({ keyid, email }, DB_TYPE); let userIds = yield this._userId.flagForRemove({ keyid, email }, DB_TYPE);
if (!userIds.length) {
util.throw(404, 'User id not found');
}
for (let userId of userIds) { for (let userId of userIds) {
yield this._email.send({ template:tpl.verifyRemove, userId, origin }); yield this._email.send({ template:tpl.verifyRemove, userId, origin });
} }
@ -172,7 +200,7 @@ class PublicKey {
// remove key document // remove key document
yield this._mongo.remove({ _id:keyid }, DB_TYPE); yield this._mongo.remove({ _id:keyid }, DB_TYPE);
// remove matching user id documents // remove matching user id documents
yield this._userid.remove({ keyid }); yield this._userId.remove({ keyid });
} }
} }

View File

@ -0,0 +1,213 @@
'use strict';
require('co-mocha')(require('mocha')); // monkey patch mocha for generators
const log = require('npmlog');
const openpgp = require('openpgp');
const nodemailer = require('nodemailer');
const Email = require('../../src/email/email');
const Mongo = require('../../src/dao/mongo');
const UserId = require('../../src/service/user-id');
const PublicKey = require('../../src/service/public-key');
const expect = require('chai').expect;
const sinon = require('sinon');
describe('Public Key Integration Tests', function() {
this.timeout(20000);
let publicKey, email, mongo, userId,
sendEmailStub, publicKeyArmored, emailParams;
const DB_TYPE_PUB_KEY = 'publickey';
const DB_TYPE_USER_ID = 'userid';
const primaryEmail = 'safewithme.testuser@gmail.com';
const origin = { host:'localhost', protocol:'http' };
before(function *() {
publicKeyArmored = require('fs').readFileSync(__dirname + '/../key1.asc', 'utf8');
let credentials;
try {
credentials = require('../../credentials.json');
} catch(e) {
log.info('mongo-test', 'No credentials.json found ... using environment vars.');
}
mongo = new Mongo({
uri: process.env.MONGO_URI || credentials.mongo.uri,
user: process.env.MONGO_USER || credentials.mongo.user,
password: process.env.MONGO_PASS || credentials.mongo.pass
});
yield mongo.connect();
});
beforeEach(function *() {
yield mongo.clear(DB_TYPE_PUB_KEY);
yield mongo.clear(DB_TYPE_USER_ID);
emailParams = null;
sendEmailStub = sinon.stub().returns(Promise.resolve({ response:'250' }));
sendEmailStub.withArgs(sinon.match(recipient => {
return recipient.to.address === primaryEmail;
}), sinon.match(params => {
emailParams = params;
return !!params.nonce;
}));
sinon.stub(nodemailer, 'createTransport').returns({
templateSender: () => { return sendEmailStub; }
});
email = new Email(nodemailer);
email.init({
host: 'localhost',
auth: { user:'user', pass:'pass' },
sender: { name:'Foo Bar', email:'foo@bar.com' }
});
userId = new UserId(mongo);
publicKey = new PublicKey(openpgp, mongo, email, userId);
});
afterEach(() => {
nodemailer.createTransport.restore();
});
after(function *() {
yield mongo.clear(DB_TYPE_PUB_KEY);
yield mongo.clear(DB_TYPE_USER_ID);
yield mongo.disconnect();
});
describe('put', () => {
it('should persist key and send verification email', function *() {
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
expect(emailParams.nonce).to.exist;
});
it('should work twice if not yet verified', function *() {
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
expect(emailParams.nonce).to.exist;
emailParams = null;
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
expect(emailParams.nonce).to.exist;
});
it('should throw 304 if key already exists', function *() {
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
yield userId.verify(emailParams);
try {
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
expect(false).to.be.true;
} catch(e) {
expect(e.status).to.equal(304);
}
});
});
describe('get', () => {
beforeEach(function *() {
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
});
it('should return verified key by key id', function *() {
yield userId.verify(emailParams);
let key = yield publicKey.get({ keyid:emailParams.keyid });
expect(key.publicKeyArmored).to.equal(publicKeyArmored);
});
it('should return verified key by email address', function *() {
yield userId.verify(emailParams);
let key = yield publicKey.get({ email:primaryEmail });
expect(key.publicKeyArmored).to.equal(publicKeyArmored);
});
it('should throw 404 for unverified key', function *() {
try {
yield publicKey.get({ keyid:emailParams.keyid });
expect(false).to.be.true;
} catch(e) {
expect(e.status).to.equal(404);
}
});
});
describe('requestRemove', () => {
let keyid;
beforeEach(function *() {
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
keyid = emailParams.keyid;
});
it('should work for verified key', function *() {
yield userId.verify(emailParams);
emailParams = null;
yield publicKey.requestRemove({ keyid, origin });
expect(emailParams.nonce).to.exist;
});
it('should work for unverified key', function *() {
emailParams = null;
yield publicKey.requestRemove({ keyid, origin });
expect(emailParams.nonce).to.exist;
});
it('should work by email address', function *() {
emailParams = null;
yield publicKey.requestRemove({ email:primaryEmail, origin });
expect(emailParams.nonce).to.exist;
});
it('should throw 404 for no key', function *() {
yield publicKey.remove({ keyid });
try {
yield publicKey.requestRemove({ keyid, origin });
expect(false).to.be.true;
} catch(e) {
expect(e.status).to.equal(404);
}
});
});
describe('verifyRemove', () => {
let keyid;
beforeEach(function *() {
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
keyid = emailParams.keyid;
emailParams = null;
yield publicKey.requestRemove({ keyid, origin });
});
it('should remove key', function *() {
yield publicKey.verifyRemove(emailParams);
let uid = yield mongo.get({ keyid }, DB_TYPE_USER_ID);
expect(uid).to.not.exist;
let key = yield mongo.get({ _id:keyid }, DB_TYPE_PUB_KEY);
expect(key).to.not.exist;
});
it('should throw 404 for no key', function *() {
yield publicKey.remove({ keyid });
try {
yield publicKey.verifyRemove(emailParams);
expect(false).to.be.true;
} catch(e) {
expect(e.status).to.equal(404);
}
});
});
describe('remove', () => {
let keyid;
beforeEach(function *() {
yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
keyid = emailParams.keyid;
});
it('should remove key', function *() {
yield publicKey.remove({ keyid });
let uid = yield mongo.get({ keyid }, DB_TYPE_USER_ID);
expect(uid).to.not.exist;
let key = yield mongo.get({ _id:keyid }, DB_TYPE_PUB_KEY);
expect(key).to.not.exist;
});
});
});