mirror of https://github.com/moparisthebest/mail
finished refactoring email dao for unit tests
This commit is contained in:
parent
0fb0e7c1e7
commit
dca3b252ce
|
@ -16,21 +16,22 @@ app.dao.DeviceStorage = function(util, crypto, jsonDao, sqlcipherDao) {
|
||||||
var i, date, key, items = [];
|
var i, date, key, items = [];
|
||||||
|
|
||||||
// format items for batch storing in dao
|
// 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
|
// put date in key if available... for easy querying
|
||||||
if (list[i].sentDate) {
|
if (i.sentDate) {
|
||||||
date = util.parseDate(list[i].sentDate);
|
date = util.parseDate(i.sentDate);
|
||||||
key = crypto.emailAddress + '_' + type + '_' + date.getTime() + '_' + list[i].id;
|
key = crypto.emailAddress + '_' + type + '_' + date.getTime() + '_' + i.id;
|
||||||
} else {
|
} else {
|
||||||
key = crypto.emailAddress + '_' + type + '_' + list[i].id;
|
key = crypto.emailAddress + '_' + type + '_' + i.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
key: key,
|
key: key,
|
||||||
object: list[i]
|
object: i
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
});
|
||||||
|
|
||||||
jsonDao.batch(items, function() {
|
jsonDao.batch(items, function() {
|
||||||
callback();
|
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 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 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)
|
* @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
|
// fetch all items of a certain type from the data-store
|
||||||
jsonDao.list(crypto.emailAddress + '_' + type, offset, num, function(encryptedList) {
|
jsonDao.list(crypto.emailAddress + '_' + type, offset, num, function(encryptedList) {
|
||||||
|
|
||||||
// decrypt list
|
callback(null, encryptedList);
|
||||||
crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) {
|
|
||||||
callback(err, decryptedList);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* A high-level Data-Access Api for handling Email synchronization
|
* A high-level Data-Access Api for handling Email synchronization
|
||||||
* between the cloud service and the device's local storage
|
* 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';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,54 +11,46 @@ app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, util) {
|
||||||
this.init = function(account, password, callback) {
|
this.init = function(account, password, callback) {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
|
|
||||||
// TODO: call getUserKeyPair to read/sync keypair with devicestorage/cloud
|
// call getUserKeyPair to read/sync keypair with devicestorage/cloud
|
||||||
|
keychain.getUserKeyPair(account.get('emailAddress'), function(err, storedKeypair) {
|
||||||
// sync user's cloud key with local storage
|
|
||||||
var storedKey = crypto.getEncryptedPrivateKey(account.get('emailAddress'));
|
|
||||||
cloudstorage.syncPrivateKey(account.get('emailAddress'), storedKey, function(err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log('Error syncing private key to cloud: ' + err);
|
callback(err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// init crypto
|
// init crypto
|
||||||
initCrypto();
|
initCrypto(storedKeypair);
|
||||||
|
|
||||||
}, function(fetchedKey) {
|
}, function(err, keypairReplacement) {
|
||||||
// replace local key with cloud key
|
if (err) {
|
||||||
crypto.putEncryptedPrivateKey(fetchedKey);
|
callback(err);
|
||||||
// whipe local storage
|
return;
|
||||||
|
}
|
||||||
|
// whipe local storage in case local keypair was replaced with cloud keypair
|
||||||
devicestorage.clear(function() {
|
devicestorage.clear(function() {
|
||||||
initCrypto();
|
// init crypto and generate new keypair
|
||||||
|
initCrypto(keypairReplacement);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function initCrypto() {
|
function initCrypto(storedKeypair) {
|
||||||
|
|
||||||
// TODO: passed fetched keypair from keychain dao
|
|
||||||
|
|
||||||
crypto.init({
|
crypto.init({
|
||||||
emailAddress: account.get('emailAddress'),
|
emailAddress: account.get('emailAddress'),
|
||||||
password: password,
|
password: password,
|
||||||
keySize: account.get('symKeySize'),
|
keySize: account.get('symKeySize'),
|
||||||
rsaKeySize: account.get('asymKeySize')
|
rsaKeySize: account.get('asymKeySize'),
|
||||||
}, function(err) {
|
storedKeypair: storedKeypair
|
||||||
|
}, function(err, generatedKeypair) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
publishPublicKey();
|
if (generatedKeypair) {
|
||||||
});
|
// persist newly generated keypair
|
||||||
}
|
keychain.putUserKeyPair(generatedKeypair, callback);
|
||||||
|
} else {
|
||||||
// TODO: refactor to be part of sync in getUserKeypair
|
callback();
|
||||||
|
}
|
||||||
function publishPublicKey() {
|
|
||||||
// get public key from crypto
|
|
||||||
var pubkey = crypto.getPublicKey();
|
|
||||||
|
|
||||||
//publish public key to cloud service
|
|
||||||
cloudstorage.putPublicKey(pubkey, function(err) {
|
|
||||||
callback(err);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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)
|
* @param num [Number] The number of items to fetch (null means fetch all)
|
||||||
*/
|
*/
|
||||||
this.listItems = function(folderName, offset, num, callback) {
|
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)
|
// check if items are in memory already (account.folders model)
|
||||||
folder = this.account.get('folders').where({
|
folder = this.account.get('folders').where({
|
||||||
|
@ -90,26 +83,56 @@ app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, util) {
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
// get items from storage
|
// get encrypted items from storage
|
||||||
devicestorage.listItems('email_' + folderName, offset, num, null, function(err, decryptedList) {
|
devicestorage.listEncryptedItems('email_' + folderName, offset, num, function(err, encryptedList) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse to backbone model collection
|
// gather public key ids required to verify signatures
|
||||||
collection = new app.model.EmailCollection(decryptedList);
|
encryptedList.forEach(function(i) {
|
||||||
|
already = null;
|
||||||
// cache collection in folder memory
|
already = _.findWhere(pubkeyIds, {
|
||||||
if (decryptedList.length > 0) {
|
_id: i.senderPk
|
||||||
folder = new app.model.Folder({
|
|
||||||
name: folderName
|
|
||||||
});
|
});
|
||||||
folder.set('items', collection);
|
if (!already) {
|
||||||
self.account.get('folders').add(folder);
|
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 {
|
} else {
|
||||||
|
|
|
@ -12,32 +12,59 @@ app.dao.KeychainDAO = function(jsonDao, cloudstorage) {
|
||||||
* @return [PublicKeyCollection] The requiested public keys
|
* @return [PublicKeyCollection] The requiested public keys
|
||||||
*/
|
*/
|
||||||
this.getPublicKeys = function(ids, callback) {
|
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
|
* 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.
|
* If no key pair exists, null is returned.
|
||||||
* return [Object] The user's key pair {publicKey, privateKey}
|
* return [Object] The user's key pair {publicKey, privateKey}
|
||||||
*/
|
*/
|
||||||
this.getUserKeyPair = function(userId, callback) {
|
this.getUserKeyPair = function(userId, callback) {
|
||||||
// loojup public key id
|
// lookup public key id
|
||||||
jsonDao.read('publickey_' + userId, function(pubkeyId) {
|
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
|
// try to read public key from local storage
|
||||||
jsonDao.read('publickey_' + pubkeyId._id, function(pubkey) {
|
jsonDao.read('publickey_' + pubkeyId._id, function(pubkey) {
|
||||||
if (!pubkey) {
|
// public key found
|
||||||
// no public key in storage
|
// get corresponding private key
|
||||||
// TODO: fetch from cloud
|
fetchEncryptedPrivateKey(pubkey);
|
||||||
// TODO: persist in local storage
|
|
||||||
callback({
|
|
||||||
errMsg: 'Not implemented yet!'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// public key found
|
|
||||||
// get corresponding private key
|
|
||||||
fetchEncryptedPrivateKey(pubkey);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -76,6 +103,8 @@ app.dao.KeychainDAO = function(jsonDao, cloudstorage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: persist in the cloud
|
||||||
|
|
||||||
// persist public key (email, _id)
|
// persist public key (email, _id)
|
||||||
var pkLookupKey = 'publickey_' + keypair.publicKey.userId;
|
var pkLookupKey = 'publickey_' + keypair.publicKey.userId;
|
||||||
jsonDao.persist(pkLookupKey, {
|
jsonDao.persist(pkLookupKey, {
|
||||||
|
|
|
@ -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 senderPubkeys = [devicestorage_test.generatedKeypair.publicKey];
|
||||||
|
|
||||||
var offset = 2,
|
var offset = 2,
|
||||||
num = 6;
|
num = 6;
|
||||||
|
|
||||||
// list items from storage (decrypted)
|
// list encrypted items from storage
|
||||||
devicestorage_test.storage.listItems('email_inbox_5', offset, num, senderPubkeys, function(err, decryptedList) {
|
devicestorage_test.storage.listEncryptedItems('email_inbox_5', offset, num, function(err, encryptedList) {
|
||||||
ok(!err);
|
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
|
var decrypted, orig = devicestorage_test.list[54];
|
||||||
for (var i = 0; i < decryptedList.length; i++) {
|
|
||||||
if (decryptedList[i].id === orig.id && decryptedList[i].from === orig.from) {
|
// check ids
|
||||||
deepEqual(decryptedList[i], orig, 'Messages decrypted correctly');
|
for (var i = 0; i < decryptedList.length; i++) {
|
||||||
break;
|
if (decryptedList[i].id === orig.id && decryptedList[i].from === orig.from) {
|
||||||
|
deepEqual(decryptedList[i], orig, 'Messages decrypted correctly');
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
start();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -23,7 +23,8 @@ asyncTest("Init", 3, function() {
|
||||||
callback();
|
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
|
// generate test data
|
||||||
emaildao_test.list = new TestData().getEmailCollection(100);
|
emaildao_test.list = new TestData().getEmailCollection(100);
|
||||||
|
@ -35,33 +36,39 @@ asyncTest("Init", 3, function() {
|
||||||
asymKeySize: emaildao_test.rsaKeySize
|
asymKeySize: emaildao_test.rsaKeySize
|
||||||
});
|
});
|
||||||
|
|
||||||
emaildao_test.emailDao.init(account, emaildao_test.password, function(err) {
|
// clear db before tests
|
||||||
ok(!err);
|
jsonDao.clear(function(err) {
|
||||||
equal(emaildao_test.emailDao.account.get('emailAddress'), emaildao_test.user, 'Email DAO Account');
|
ok(!err, 'DB cleared. Error status: ' + err);
|
||||||
|
|
||||||
// clear db before tests
|
emaildao_test.emailDao.init(account, emaildao_test.password, function(err) {
|
||||||
jsonDao.clear(function(err) {
|
ok(!err);
|
||||||
ok(!err, 'DB cleared. Error status: ' + err);
|
equal(emaildao_test.emailDao.account.get('emailAddress'), emaildao_test.user, 'Email DAO Account');
|
||||||
|
|
||||||
start();
|
start();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("Persist test emails", 3, function() {
|
asyncTest("Persist test emails", 4, function() {
|
||||||
emaildao_test.crypto.encryptListForUser(emaildao_test.list.toJSON(), null, function(err, encryptedList) {
|
emaildao_test.keychain.getUserKeyPair(emaildao_test.user, function(err, keypair) {
|
||||||
ok(!err);
|
ok(!err && keypair, 'Fetch keypair from keychain');
|
||||||
equal(encryptedList.length, emaildao_test.list.length, 'Encrypt list');
|
|
||||||
|
|
||||||
// add sent date to encrypted items
|
var receiverPubkeys = [keypair.publicKey];
|
||||||
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() {
|
emaildao_test.crypto.encryptListForUser(emaildao_test.list.toJSON(), receiverPubkeys, function(err, encryptedList) {
|
||||||
ok(true, 'Store encrypted list');
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue