finished refactoring email dao for unit tests

This commit is contained in:
Tankred Hase 2013-05-31 15:51:34 +02:00
parent 0fb0e7c1e7
commit dca3b252ce
6 changed files with 175 additions and 106 deletions

View File

@ -16,21 +16,22 @@ app.dao.DeviceStorage = function(util, crypto, jsonDao, sqlcipherDao) {
var i, date, key, items = [];
// format items for batch storing in dao
for (i = 0; i < list.length; i++) {
list.forEach(function(i) {
// put date in key if available... for easy querying
if (list[i].sentDate) {
date = util.parseDate(list[i].sentDate);
key = crypto.emailAddress + '_' + type + '_' + date.getTime() + '_' + list[i].id;
if (i.sentDate) {
date = util.parseDate(i.sentDate);
key = crypto.emailAddress + '_' + type + '_' + date.getTime() + '_' + i.id;
} else {
key = crypto.emailAddress + '_' + type + '_' + list[i].id;
key = crypto.emailAddress + '_' + type + '_' + i.id;
}
items.push({
key: key,
object: list[i]
object: i
});
}
});
jsonDao.batch(items, function() {
callback();
@ -38,20 +39,16 @@ app.dao.DeviceStorage = function(util, crypto, jsonDao, sqlcipherDao) {
};
/**
* Decrypts the stored items of a given type and returns them
* List stored items of a given type
* @param type [String] The type of item e.g. 'email'
* @param offset [Number] The offset of items to fetch (0 is the last stored item)
* @param num [Number] The number of items to fetch (null means fetch all)
*/
this.listItems = function(type, offset, num, senderPubkeys, callback) {
this.listEncryptedItems = function(type, offset, num, callback) {
// fetch all items of a certain type from the data-store
jsonDao.list(crypto.emailAddress + '_' + type, offset, num, function(encryptedList) {
// decrypt list
crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) {
callback(err, decryptedList);
});
callback(null, encryptedList);
});
};

View File

@ -2,7 +2,7 @@
* A high-level Data-Access Api for handling Email synchronization
* between the cloud service and the device's local storage
*/
app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, util) {
app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, util, keychain) {
'use strict';
/**
@ -11,54 +11,46 @@ app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, util) {
this.init = function(account, password, callback) {
this.account = account;
// TODO: call getUserKeyPair to read/sync keypair with devicestorage/cloud
// sync user's cloud key with local storage
var storedKey = crypto.getEncryptedPrivateKey(account.get('emailAddress'));
cloudstorage.syncPrivateKey(account.get('emailAddress'), storedKey, function(err) {
// call getUserKeyPair to read/sync keypair with devicestorage/cloud
keychain.getUserKeyPair(account.get('emailAddress'), function(err, storedKeypair) {
if (err) {
console.log('Error syncing private key to cloud: ' + err);
callback(err);
return;
}
// init crypto
initCrypto();
initCrypto(storedKeypair);
}, function(fetchedKey) {
// replace local key with cloud key
crypto.putEncryptedPrivateKey(fetchedKey);
// whipe local storage
}, function(err, keypairReplacement) {
if (err) {
callback(err);
return;
}
// whipe local storage in case local keypair was replaced with cloud keypair
devicestorage.clear(function() {
initCrypto();
// init crypto and generate new keypair
initCrypto(keypairReplacement);
});
});
function initCrypto() {
// TODO: passed fetched keypair from keychain dao
function initCrypto(storedKeypair) {
crypto.init({
emailAddress: account.get('emailAddress'),
password: password,
keySize: account.get('symKeySize'),
rsaKeySize: account.get('asymKeySize')
}, function(err) {
rsaKeySize: account.get('asymKeySize'),
storedKeypair: storedKeypair
}, function(err, generatedKeypair) {
if (err) {
callback(err);
return;
}
publishPublicKey();
});
}
// TODO: refactor to be part of sync in getUserKeypair
function publishPublicKey() {
// get public key from crypto
var pubkey = crypto.getPublicKey();
//publish public key to cloud service
cloudstorage.putPublicKey(pubkey, function(err) {
callback(err);
if (generatedKeypair) {
// persist newly generated keypair
keychain.putUserKeyPair(generatedKeypair, callback);
} else {
callback();
}
});
}
};
@ -82,7 +74,8 @@ app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, util) {
* @param num [Number] The number of items to fetch (null means fetch all)
*/
this.listItems = function(folderName, offset, num, callback) {
var collection, folder, self = this;
var collection, folder, already, pubkeyIds = [],
self = this;
// check if items are in memory already (account.folders model)
folder = this.account.get('folders').where({
@ -90,26 +83,56 @@ app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, util) {
})[0];
if (!folder) {
// get items from storage
devicestorage.listItems('email_' + folderName, offset, num, null, function(err, decryptedList) {
// get encrypted items from storage
devicestorage.listEncryptedItems('email_' + folderName, offset, num, function(err, encryptedList) {
if (err) {
callback(err);
return;
}
// parse to backbone model collection
collection = new app.model.EmailCollection(decryptedList);
// cache collection in folder memory
if (decryptedList.length > 0) {
folder = new app.model.Folder({
name: folderName
// gather public key ids required to verify signatures
encryptedList.forEach(function(i) {
already = null;
already = _.findWhere(pubkeyIds, {
_id: i.senderPk
});
folder.set('items', collection);
self.account.get('folders').add(folder);
}
if (!already) {
pubkeyIds.push({
_id: i.senderPk
});
}
});
callback(null, collection);
// fetch public keys from keychain
keychain.getPublicKeys(pubkeyIds, function(err, senderPubkeys) {
if (err) {
callback(err);
return;
}
// decrypt list
crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) {
if (err) {
callback(err);
return;
}
// parse to backbone model collection
collection = new app.model.EmailCollection(decryptedList);
// cache collection in folder memory
if (decryptedList.length > 0) {
folder = new app.model.Folder({
name: folderName
});
folder.set('items', collection);
self.account.get('folders').add(folder);
}
callback(null, collection);
});
});
});
} else {

View File

@ -12,32 +12,59 @@ app.dao.KeychainDAO = function(jsonDao, cloudstorage) {
* @return [PublicKeyCollection] The requiested public keys
*/
this.getPublicKeys = function(ids, callback) {
var already, pubkeys = [];
var after = _.after(ids.length, function() {
callback(null, pubkeys);
});
_.each(ids, function(i) {
// try to read public key from local storage
jsonDao.read('publickey_' + i._id, function(pubkey) {
if (!pubkey) {
// TODO: fetch from cloud storage
callback({
errMsg: 'Not implemented yet!'
});
return;
}
// check if public key with that id has already been fetched
already = null;
already = _.findWhere(pubkeys, {
_id: i._id
});
if (!already) {
pubkeys.push(pubkey);
}
after(); // asynchronously iterate through objects
});
});
};
/**
* Gets the local user's key either from local storage
* or syncronizes from the cloud. The private key is encrypted.
* or fetches it from the cloud. The private key is encrypted.
* If no key pair exists, null is returned.
* return [Object] The user's key pair {publicKey, privateKey}
*/
this.getUserKeyPair = function(userId, callback) {
// loojup public key id
// lookup public key id
jsonDao.read('publickey_' + userId, function(pubkeyId) {
if (!pubkeyId || !pubkeyId._id) {
// no public key in storage
// TODO: fetch from cloud
// TODO: persist in local storage
callback();
return;
}
// try to read public key from local storage
jsonDao.read('publickey_' + pubkeyId._id, function(pubkey) {
if (!pubkey) {
// no public key in storage
// TODO: fetch from cloud
// TODO: persist in local storage
callback({
errMsg: 'Not implemented yet!'
});
} else {
// public key found
// get corresponding private key
fetchEncryptedPrivateKey(pubkey);
}
// public key found
// get corresponding private key
fetchEncryptedPrivateKey(pubkey);
});
});
@ -76,6 +103,8 @@ app.dao.KeychainDAO = function(jsonDao, cloudstorage) {
return;
}
// TODO: persist in the cloud
// persist public key (email, _id)
var pkLookupKey = 'publickey_' + keypair.publicKey.userId;
jsonDao.persist(pkLookupKey, {

View File

@ -59,28 +59,33 @@ asyncTest("Store encrypted list", 1, function() {
});
});
asyncTest("List items", 3, function() {
asyncTest("List items", 4, function() {
var senderPubkeys = [devicestorage_test.generatedKeypair.publicKey];
var offset = 2,
num = 6;
// list items from storage (decrypted)
devicestorage_test.storage.listItems('email_inbox_5', offset, num, senderPubkeys, function(err, decryptedList) {
// list encrypted items from storage
devicestorage_test.storage.listEncryptedItems('email_inbox_5', offset, num, function(err, encryptedList) {
ok(!err);
equal(decryptedList.length, num, 'Found ' + decryptedList.length + ' items in store (and decrypted)');
var decrypted, orig = devicestorage_test.list[54];
// decrypt list
devicestorage_test.crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) {
ok(!err);
equal(decryptedList.length, num, 'Found ' + decryptedList.length + ' items in store (and decrypted)');
// check ids
for (var i = 0; i < decryptedList.length; i++) {
if (decryptedList[i].id === orig.id && decryptedList[i].from === orig.from) {
deepEqual(decryptedList[i], orig, 'Messages decrypted correctly');
break;
var decrypted, orig = devicestorage_test.list[54];
// check ids
for (var i = 0; i < decryptedList.length; i++) {
if (decryptedList[i].id === orig.id && decryptedList[i].from === orig.from) {
deepEqual(decryptedList[i], orig, 'Messages decrypted correctly');
break;
}
}
}
start();
start();
});
});
});

View File

@ -23,7 +23,8 @@ asyncTest("Init", 3, function() {
callback();
}
};
emaildao_test.emailDao = new app.dao.EmailDAO(_, emaildao_test.crypto, emaildao_test.storage, cloudstorageStub, util);
emaildao_test.keychain = new app.dao.KeychainDAO(jsonDao, cloudstorageStub);
emaildao_test.emailDao = new app.dao.EmailDAO(_, emaildao_test.crypto, emaildao_test.storage, cloudstorageStub, util, emaildao_test.keychain);
// generate test data
emaildao_test.list = new TestData().getEmailCollection(100);
@ -35,33 +36,39 @@ asyncTest("Init", 3, function() {
asymKeySize: emaildao_test.rsaKeySize
});
emaildao_test.emailDao.init(account, emaildao_test.password, function(err) {
ok(!err);
equal(emaildao_test.emailDao.account.get('emailAddress'), emaildao_test.user, 'Email DAO Account');
// clear db before tests
jsonDao.clear(function(err) {
ok(!err, 'DB cleared. Error status: ' + err);
// clear db before tests
jsonDao.clear(function(err) {
ok(!err, 'DB cleared. Error status: ' + err);
emaildao_test.emailDao.init(account, emaildao_test.password, function(err) {
ok(!err);
equal(emaildao_test.emailDao.account.get('emailAddress'), emaildao_test.user, 'Email DAO Account');
start();
});
});
});
asyncTest("Persist test emails", 3, function() {
emaildao_test.crypto.encryptListForUser(emaildao_test.list.toJSON(), null, function(err, encryptedList) {
ok(!err);
equal(encryptedList.length, emaildao_test.list.length, 'Encrypt list');
asyncTest("Persist test emails", 4, function() {
emaildao_test.keychain.getUserKeyPair(emaildao_test.user, function(err, keypair) {
ok(!err && keypair, 'Fetch keypair from keychain');
// add sent date to encrypted items
for (var i = 0; i < encryptedList.length; i++) {
encryptedList[i].sentDate = emaildao_test.list.at(i).get('sentDate');
}
var receiverPubkeys = [keypair.publicKey];
emaildao_test.storage.storeEcryptedList(encryptedList, 'email_inbox', function() {
ok(true, 'Store encrypted list');
emaildao_test.crypto.encryptListForUser(emaildao_test.list.toJSON(), receiverPubkeys, function(err, encryptedList) {
ok(!err);
equal(encryptedList.length, emaildao_test.list.length, 'Encrypt list');
start();
// add sent date to encrypted items
for (var i = 0; i < encryptedList.length; i++) {
encryptedList[i].sentDate = emaildao_test.list.at(i).get('sentDate');
}
emaildao_test.storage.storeEcryptedList(encryptedList, 'email_inbox', function() {
ok(true, 'Store encrypted list');
start();
});
});
});
});

View File

@ -66,6 +66,14 @@ asyncTest("Get User Keypair", 2, function() {
});
});
// asyncTest("Get Public Keys", 1, function() {
asyncTest("Get Public Keys", 2, function() {
var pubkeyIds = [{
_id: keychaindao_test.keypair.publicKey._id
}];
keychaindao_test.keychainDao.getPublicKeys(pubkeyIds, function(err, pubkeys) {
ok(!err);
deepEqual(pubkeys[0], keychaindao_test.keypair.publicKey, "Fetch public key");
// });
start();
});
});