From 1dd906afcd61f0861a7882f99a26ee53765abf34 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 29 Sep 2014 12:11:10 -0300 Subject: [PATCH] add sinon for fromObj --- js/models/Identity.js | 66 ++++----- js/models/Storage.js | 327 ++++++++++++++++++++++++++++++++++++++++++ js/models/Wallet.js | 2 +- test/test.Identity.js | 4 +- 4 files changed, 356 insertions(+), 43 deletions(-) create mode 100644 js/models/Storage.js diff --git a/js/models/Identity.js b/js/models/Identity.js index 8a80a0fdf..1ff9f0b78 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -78,6 +78,18 @@ Identity._newStorage = function(opts) { return new Storage(opts); }; +/* for stubbing */ +Identity.prototype._newWallet = function(opts) { + return new Wallet(opts); +}; + +/* for stubbing */ +Identity._walletFromObj = function(o, s, n, b, skip) { + return Wallet.fromObj(o, s, n, b, skip); +}; + + + /** * creates and Identity @@ -169,19 +181,6 @@ Identity.prototype.store = function(opts, cb) { }); }; - -/** - * @desc obtain network name from serialized wallet - * @param {Object} wallet object - * @return {string} network name - */ -Identity.prototype.obtainNetworkName = function(obj) { - return obj.networkName || - (obj.opts ? obj.opts.networkName : null) || - (obj.publicKeyRing ? obj.publicKeyRing.networkName : null) || - obj.privateKey.networkName; -}; - /** * @desc Imports a wallet from an encrypted base64 object * @param {string} base64 - the base64 encoded object @@ -195,14 +194,12 @@ Identity.prototype.importWallet = function(base64, passphrase, skipFields) { var obj = this.storage.decrypt(base64); if (!obj) return false; - var w = Wallet.fromObj(obj, this.storage, this.networks[networkName], this.blockchains[networkName]); + var networkName = Wallet.obtainNetworkName(obj); + var w = Identity._walletFromObj(obj, this.storage, this.networks[networkName], this.blockchains[networkName]); this._checkVersion(w.version); - - this.profile.addWallet(w.id,function(err){ + this.addWallet(w.id, function(err) { if (err) return cb(err); - - - w.store(); + w.store(cb); }); }; @@ -296,19 +293,6 @@ Identity.prototype.read_Old = function(walletId, skipFields, cb) { return cb(err, w); }); }; - -/** - * @desc This method instantiates a wallet. Usefull for stubbing. - * - * @param {opts} opts, ready for new Wallet(opts) - * - */ - - -Identity.prototype._newWallet = function(opts) { - return new Wallet(opts); -}; - /** * @desc This method prepares options for a new Wallet * @@ -385,12 +369,13 @@ Identity.prototype.createWallet = function(opts, cb) { this.addWallet(w, function(err) { if (err) return cb(err); self.profile.setLastOpenedTs(w.id, function(err) { - return cb(err, w); + return cb(err, w); }); }); }; Identity.prototype.addWallet = function(wallet, cb) { + preconditions.checkArgument(wallet); preconditions.checkArgument(wallet.id); preconditions.checkArgument(cb); @@ -400,9 +385,8 @@ Identity.prototype.addWallet = function(wallet, cb) { self.wallets.push(w); - self.store(function(err) { - if (err) return cb(err); - return (err); + w.store(function(err) { + return cb(err); }); }); }; @@ -414,10 +398,12 @@ Identity.prototype.addWallet = function(wallet, cb) { * @throws {Error} if there's a major version difference */ Identity.prototype._checkVersion = function(inVersion) { - var thisV = this.version.split('.'); - var thisV0 = parseInt(thisV[0]); - var inV = inVersion.split('.'); - var inV0 = parseInt(inV[0]); + if (inVersion) { + var thisV = this.version.split('.'); + var thisV0 = parseInt(thisV[0]); + var inV = inVersion.split('.'); + var inV0 = parseInt(inV[0]); + } //We only check for major version differences if (thisV0 < inV0) { diff --git a/js/models/Storage.js b/js/models/Storage.js new file mode 100644 index 000000000..0f576722f --- /dev/null +++ b/js/models/Storage.js @@ -0,0 +1,327 @@ +'use strict'; +var preconditions = require('preconditions').singleton(); +var CryptoJS = require('node-cryptojs-aes').CryptoJS; +var bitcore = require('bitcore'); +var preconditions = require('preconditions').instance(); +var _ = require('underscore'); +var CACHE_DURATION = 1000 * 60 * 5; +var id = 0; + + +/* + * Storage wraps db plugin primitives + * with encryption functionalities + * and adds from some extra functionalities + * and a common interfase + */ +function Storage(opts) { + preconditions.checkArgument(opts); + preconditions.checkArgument(opts.password); + opts = opts || {}; + + this.wListCache = {}; + this.__uniqueid = ++id; + this.setPassphrase(opts.password); + + try { + this.db = opts.db || localStorage; + this.sessionStorage = opts.sessionStorage || sessionStorage; + } catch (e) { + console.log('Error in storage:', e); //TODO + }; + + preconditions.checkState(this.db, 'No db defined'); + preconditions.checkState(this.sessionStorage, 'No sessionStorage defined'); +} + +var pps = {}; +Storage.prototype._getPassphrase = function() { + + if (!pps[this.__uniqueid]) + throw new Error('NOPASSPHRASE: No passphrase set'); + + return pps[this.__uniqueid]; +} + +Storage.prototype.setPassphrase = function(password) { + pps[this.__uniqueid] = password; +} + +Storage.prototype._encrypt = function(string) { + var encrypted = CryptoJS.AES.encrypt(string, this._getPassphrase()); + var encryptedBase64 = encrypted.toString(); + return encryptedBase64; +}; + +Storage.prototype._decrypt = function(base64) { + var decryptedStr = null; + try { + var decrypted = CryptoJS.AES.decrypt(base64, this._getPassphrase()); + if (decrypted) + decryptedStr = decrypted.toString(CryptoJS.enc.Utf8); + } catch (e) { + // Error while decrypting + return null; + } + return decryptedStr; +}; + + +Storage.prototype._read = function(k, cb) { + preconditions.checkArgument(cb); + + var self = this; + this.db.getItem(k, function(ret) { + if (!ret) return cb(null); + var ret = self._decrypt(ret); + if (!ret) return cb(null); + + ret = ret.toString(CryptoJS.enc.Utf8); + ret = JSON.parse(ret); + return cb(ret); + }); +}; + +Storage.prototype._write = function(k, v, cb) { + preconditions.checkArgument(cb); + + v = JSON.stringify(v); + v = this._encrypt(v); + this.db.setItem(k, v, cb); +}; + +// get value by key +Storage.prototype.getGlobal = function(k, cb) { + preconditions.checkArgument(cb); + + this.db.getItem(k, function(item) { + cb(item == 'undefined' ? undefined : item); + }); +}; + +// set value for key +Storage.prototype.setGlobal = function(k, v, cb) { + preconditions.checkArgument(cb); + this.db.setItem(k, typeof v === 'object' ? JSON.stringify(v) : v, cb); +}; + +// remove value for key +Storage.prototype.removeGlobal = function(k, cb) { + preconditions.checkArgument(cb); + this.db.removeItem(k, cb); +}; + +Storage.prototype.getSessionId = function(cb) { + preconditions.checkArgument(cb); + var self = this; + + self.sessionStorage.getItem('sessionId', function(sessionId) { + if (sessionId) + return cb(sessionId); + + sessionId = bitcore.SecureRandom.getRandomBuffer(8).toString('hex'); + self.sessionStorage.setItem('sessionId', sessionId, function() { + return cb(sessionId); + }); + }); +}; + +Storage.prototype.setSessionId = function(sessionId, cb) { + this.sessionStorage.setItem('sessionId', sessionId, cb); +}; + +Storage.prototype.get = function(key, cb) { + var self = this; + self._read(key, function(v) { + return cb(null, v); + }) +}; + +Storage.prototype.getFirst = function(prefix, cb) { + var self = this; + this.db.allKeys(function(allKeys) { + var keys = _.filter(allKeys, function(k) { + if ((k === prefix) || k.indexOf(prefix) === 0) return true; + }); + if (keys.length === 0) return cb(new Error('not found')); + self._read(keys[0], function(v) { + if (_.isNull(v)) return cb(new Error('Could not decrypt data')); + return cb(null, v, keys[0]); + }) + }); +}; + +Storage.prototype.set = function(key, obj, cb) { + preconditions.checkArgument(key); + preconditions.checkArgument(cb); + this._write(key, obj, function() { + return cb(); + }); +}; + +Storage.prototype.delete = function(key, cb) { + preconditions.checkArgument(cb); + this.removeGlobal(key, function() { + return cb(); + }); +}; + +Storage.prototype.deletePrefix = function(prefix, cb) { + storage.getFirst(prefix, function(err, v, k) { + if (err && !v) return cb(err); + + storage.delete(k, function(err) { + if (err) return cb(err); + storage.deletePrefix(prefix, cb); + }) + }); +}; + + +Storage.prototype.clearAll = function(cb) { + this.sessionStorage.clear(); + this.db.clear(cb); +}; + +Storage.prototype.decrypt = function(base64) { + var decryptedStr = this._decrypt(base64); + return JSON.parse(decryptedStr); +}; + +Storage.prototype.encrypt = function(obj) { + var string = JSON.stringify(obj); + return this._encrypt(string); +}; + +/* + * OLD functions, only for temporary backwards compatibility + */ + + +Storage.prototype.readWallet_Old = function(walletId, cb) { + var self = this; + this.db.allKeys(function(allKeys) { + var obj = {}; + var keys = _.filter(allKeys, function(k) { + if (k.indexOf(walletId + '::') === 0) return true; + }); + if (keys.length === 0) return cb(new Error('Wallet ' + walletId + ' not found')); + var count = keys.length; + _.each(keys, function(k) { + self._read(k, function(v) { + obj[k.split('::')[1]] = v; + if (--count === 0) return cb(null, obj); + }) + }); + }); +}; + + +Storage.prototype.deleteWallet_Old = function(walletId, cb) { + preconditions.checkArgument(walletId); + preconditions.checkArgument(cb); + var err; + var self = this; + + var toDelete = {}; + + this.db.allKeys(function(allKeys) { + for (var ii in allKeys) { + var key = allKeys[ii]; + var split = key.split('::'); + if (split.length == 2 && split[0] === walletId) { + toDelete[key] = 1; + }; + } + var l = Object.keys(toDelete).length, + j = 0; + if (!l) + return cb(new Error('WNOTFOUND: Wallet not found')); + + toDelete['nameFor::' + walletId] = 1; + l++; + + for (var i in toDelete) { + self.removeGlobal(i, function() { + if (++j == l) + return cb(err); + }); + + } + }); +}; + + +// TODO +Storage.prototype._getWalletIds_Old = function(cb) { + preconditions.checkArgument(cb); + var walletIds = []; + var uniq = {}; + this.db.allKeys(function(keys) { + for (var ii in keys) { + var key = keys[ii]; + var split = key.split('::'); + if (split.length == 2) { + var walletId = split[0]; + + if (!walletId || walletId === 'nameFor' || walletId === 'lock' || walletId === 'wallet') + continue; + + if (typeof uniq[walletId] === 'undefined') { + walletIds.push(walletId); + uniq[walletId] = 1; + } + } + } + return cb(walletIds); + }); +}; + +// TODO +Storage.prototype.getWallets1_Old = function(cb) { + preconditions.checkArgument(cb); + + if (this.wListCache.ts > Date.now()) + return cb(this.wListCache.data) + + var wallets = []; + var self = this; + + this._getWalletIds_Old(function(ids) { + var l = ids.length, + i = 0; + if (!l) + return cb([]); + + _.each(ids, function(id) { + self.getGlobal('nameFor::' + id, function(name) { + wallets.push({ + id: id, + name: name, + }); + if (++i == l) { + self.wListCache.data = wallets; + self.wListCache.ts = Date.now() + CACHE_DURATION; + return cb(wallets); + } + }); + }); + }); +}; + + +Storage.prototype.getWallets_Old = function(cb) { + var self = this; + self.getWallets2_Old(function(wallets) { + self.getWallets1_Old(function(wallets2) { + var ids = _.pluck(wallets, 'id'); + _.each(wallets2, function(w) { + if (!_.contains(ids, w.id)) + wallets.push(w); + }); + return cb(wallets); + }); + }) +}; + +module.exports = Storage; diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 8d501ae26..adaf1bc0c 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -209,7 +209,7 @@ Wallet.obtainNetworkName = function(obj) { return obj.networkName || (obj.opts ? obj.opts.networkName : null) || (obj.publicKeyRing ? obj.publicKeyRing.networkName : null) || - obj.privateKey.networkName; + (obj.publicKeyRing ? obj.privateKey.networkName : null); }; diff --git a/test/test.Identity.js b/test/test.Identity.js index 8b1a25456..4f775f5aa 100644 --- a/test/test.Identity.js +++ b/test/test.Identity.js @@ -228,8 +228,8 @@ describe('Identity model', function() { describe.only('#importWallet', function() { it('should create wallet from encrypted object', function() { iden.storage.setPassphrase = sinon.spy(); - iden.storage.decrypt = sinon.stub().withArgs('base64').returns('walletObj'); - iden.fromObj = sinon.stub().withArgs('walletObj').returns('ok'); + iden.storage.decrypt = sinon.stub().withArgs('base64').returns({networkName:'testnet'}); + Identity._walletFromObj = sinon.stub().withArgs('walletObj').returns('ok'); var w = iden.importWallet("encrypted object", "123");