Merged all pending work done on the private repository

This commit is contained in:
Giulio Cesare Solaroli 2015-06-27 19:08:20 +02:00
parent 83b40aea50
commit e02ba2c20c
54 changed files with 4535 additions and 1659 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -26,7 +26,7 @@
"ligatures": "generate password",
"name": "key"
},
"setIdx": 1,
"setIdx": 2,
"setId": 7,
"iconIdx": 141
},
@ -55,7 +55,7 @@
"ligatures": "view password",
"name": "eye"
},
"setIdx": 1,
"setIdx": 2,
"setId": 7,
"iconIdx": 206
},
@ -81,7 +81,7 @@
"name": "tag",
"ligatures": "tag"
},
"setIdx": 2,
"setIdx": 3,
"setId": 6,
"iconIdx": 0
},
@ -108,7 +108,7 @@
"name": "tags",
"ligatures": "tags"
},
"setIdx": 2,
"setIdx": 3,
"setId": 6,
"iconIdx": 1
},
@ -135,7 +135,7 @@
"name": "clock",
"ligatures": "recent"
},
"setIdx": 2,
"setIdx": 3,
"setId": 6,
"iconIdx": 2
},
@ -164,7 +164,7 @@
"name": "spinner",
"ligatures": "loading"
},
"setIdx": 2,
"setIdx": 3,
"setId": 6,
"iconIdx": 3
},
@ -192,7 +192,7 @@
"name": "search",
"ligatures": "search"
},
"setIdx": 2,
"setIdx": 3,
"setId": 6,
"iconIdx": 4
},
@ -220,7 +220,7 @@
"name": "locked",
"ligatures": "locked"
},
"setIdx": 2,
"setIdx": 3,
"setId": 6,
"iconIdx": 6
},
@ -246,7 +246,7 @@
"name": "unlocked",
"ligatures": "unlocked"
},
"setIdx": 2,
"setIdx": 3,
"setId": 6,
"iconIdx": 7
},
@ -275,7 +275,7 @@
"name": "menu",
"ligatures": "menu"
},
"setIdx": 2,
"setIdx": 3,
"setId": 6,
"iconIdx": 8
},
@ -304,7 +304,7 @@
"name": "close",
"ligatures": "failure, failed, delete, clear, cancel, close"
},
"setIdx": 2,
"setIdx": 3,
"setId": 6,
"iconIdx": 9
},
@ -333,7 +333,7 @@
"name": "checkmark",
"ligatures": "done, ok, save"
},
"setIdx": 2,
"setIdx": 3,
"setId": 6,
"iconIdx": 10
},
@ -348,8 +348,10 @@
"grid": 0,
"tags": [
"commands"
]
],
"attrs": []
},
"attrs": [],
"properties": {
"order": 18,
"id": 1,
@ -358,7 +360,7 @@
"name": "commands",
"ligatures": "commands"
},
"setIdx": 3,
"setIdx": 4,
"setId": 4,
"iconIdx": 0
},
@ -371,8 +373,10 @@
"grid": 0,
"tags": [
"logo"
]
],
"attrs": []
},
"attrs": [],
"properties": {
"order": 4,
"id": 0,
@ -381,7 +385,7 @@
"name": "logo",
"ligatures": "clipperz"
},
"setIdx": 3,
"setIdx": 4,
"setId": 4,
"iconIdx": 1
},
@ -398,8 +402,10 @@
"envelope",
"contact"
],
"grid": 20
"grid": 20,
"attrs": []
},
"attrs": [],
"properties": {
"id": 4,
"order": 19,
@ -408,7 +414,7 @@
"name": "mail",
"ligatures": "email"
},
"setIdx": 6,
"setIdx": 7,
"setId": 0,
"iconIdx": 4
},
@ -422,8 +428,10 @@
"popout",
"new window"
],
"grid": 20
"grid": 20,
"attrs": []
},
"attrs": [],
"properties": {
"id": 35,
"order": 9,
@ -432,7 +440,7 @@
"name": "popup",
"ligatures": "url, direct login"
},
"setIdx": 6,
"setIdx": 7,
"setId": 0,
"iconIdx": 35
},
@ -446,17 +454,19 @@
"add",
"sum"
],
"grid": 20
"grid": 20,
"attrs": []
},
"attrs": [],
"properties": {
"id": 125,
"order": 14,
"prevSize": 32,
"code": 58895,
"name": "plus",
"ligatures": "add new field"
"ligatures": "add new field, create new OTP"
},
"setIdx": 6,
"setIdx": 7,
"setId": 0,
"iconIdx": 125
},
@ -472,17 +482,19 @@
"remove",
"delete"
],
"grid": 20
"grid": 20,
"attrs": []
},
"attrs": [],
"properties": {
"id": 126,
"order": 17,
"prevSize": 32,
"code": 58898,
"name": "cross",
"ligatures": "remove field, remove tag"
"ligatures": "remove field, remove tag, remove OTP"
},
"setIdx": 6,
"setIdx": 7,
"setId": 0,
"iconIdx": 126
},
@ -496,8 +508,10 @@
"add",
"sum"
],
"grid": 20
"grid": 20,
"attrs": []
},
"attrs": [],
"properties": {
"id": 128,
"order": 20,
@ -506,7 +520,7 @@
"name": "plus3",
"ligatures": "add card"
},
"setIdx": 6,
"setIdx": 7,
"setId": 0,
"iconIdx": 128
},
@ -520,8 +534,10 @@
"left",
"previous"
],
"grid": 20
"grid": 20,
"attrs": []
},
"attrs": [],
"properties": {
"id": 205,
"order": 11,
@ -530,7 +546,7 @@
"name": "arrow-left",
"ligatures": "back"
},
"setIdx": 6,
"setIdx": 7,
"setId": 0,
"iconIdx": 205
},
@ -544,8 +560,10 @@
"right",
"next"
],
"grid": 20
"grid": 20,
"attrs": []
},
"attrs": [],
"properties": {
"id": 208,
"order": 12,
@ -554,7 +572,7 @@
"name": "arrow-right",
"ligatures": "show detail"
},
"setIdx": 6,
"setIdx": 7,
"setId": 0,
"iconIdx": 208
}
@ -584,7 +602,8 @@
"showMetadata": false,
"autoHost": false,
"embed": true,
"showVersion": true
"showVersion": true,
"ligaReset": "tags"
},
"imagePref": {
"color": 0,

View File

@ -110,7 +110,8 @@ Clipperz_normalizedNewLine = '\x0d\x0a';
// Clipperz.PM.Proxy.defaultProxy = new Clipperz.PM.Proxy.Offline({ 'dataStore':dataStore, 'type':'OFFLINE_COPY', 'typeDescription':'Offline copy'});
Clipperz.PM.Proxy.defaultProxy = new Clipperz.PM.Proxy.Offline({ 'dataStore':dataStore, 'type':'ONLINE', 'typeDescription':'Offline copy'});
Clipperz.PM.Proxy.defaultProxy.dataStore().setupWithEncryptedData(testData);
</script>
<script>
// Live Reload hoock
document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')
</script>

View File

@ -82,6 +82,7 @@ MochiKit.Base.update(Clipperz.Base, {
return MochiKit.Base.compare(a[aKey].toLowerCase(), b[aKey].toLowerCase());
}
},
//-------------------------------------------------------------------------
/*
'dependsOn': function(module, deps) {
@ -111,6 +112,10 @@ MochiKit.Base.update(Clipperz.Base, {
return aValue.replace(/^\s+|\s+$/g, "");
},
'zipWithRange': function (anArray) {
return MochiKit.Base.zip(MochiKit.Iter.range(anArray.length), anArray);
},
//-------------------------------------------------------------------------
'stringToByteArray': function (aValue) {

View File

@ -240,13 +240,13 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
//-------------------------------------------------------------------------
'updateCredentials': function (aUsername, aPassphrase, someUserData) {
'updateCredentials': function (someData) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("Connection.updateCredentials", {trace:false});
deferredResult.collectResults({
'credentials': [
MochiKit.Base.method(this, 'normalizedCredentials', {username:aUsername, password:aPassphrase}),
MochiKit.Base.method(this, 'normalizedCredentials', {username:someData['newUsername'], password:someData['newPassphrase']}),
MochiKit.Base.bind(function(someCredentials) {
var srpConnection;
var result;
@ -258,15 +258,13 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
return result;
}, this)
],
'user': MochiKit.Base.partial(MochiKit.Async.succeed, someUserData)
'user': MochiKit.Base.partial(MochiKit.Async.succeed, someData['user']),
'oneTimePasswords': MochiKit.Base.partial(MochiKit.Async.succeed, someData['oneTimePasswords'])
});
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
deferredResult.addMethod(this, 'message', 'upgradeUserCredentials');
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
deferredResult.callback();
return deferredResult;
},
//=========================================================================
@ -309,12 +307,12 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
'message': 'oneTimePassword',
'version': Clipperz.PM.Connection.communicationProtocol.currentVersion,
'parameters': {
'oneTimePasswordKey': Clipperz.PM.DataModel.OneTimePassword.computeKeyWithUsernameAndPassword(someParameters['username'], normalizedOTP),
'oneTimePasswordKey': Clipperz.PM.DataModel.OneTimePassword.computeKeyWithPassword(normalizedOTP),
'oneTimePasswordKeyChecksum': Clipperz.PM.DataModel.OneTimePassword.computeKeyChecksumWithUsernameAndPassword(someParameters['username'], normalizedOTP)
}
}
return Clipperz.Async.callbacks("Connction.redeemOTP", [
return Clipperz.Async.callbacks("Connction.redeemOneTimePassword", [
MochiKit.Base.method(this.proxy(), 'handshake', args),
function(aResult) {
return Clipperz.PM.Crypto.deferredDecrypt({
@ -364,8 +362,6 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
return result;
});
deferredResult.addMethod(this.proxy(), 'handshake');
// deferredResult.addCallbackPass(MochiKit.Signal.signal, this, 'updatedProgressState', 'connection_credentialVerification');
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
deferredResult.addCallback(function(someParameters) {
var result;
@ -415,10 +411,6 @@ Clipperz.PM.Connection.SRP['1.0'].prototype = MochiKit.Base.update(new Clipperz.
return someParameters;
}, this));
// deferredResult.addCallbackPass(MochiKit.Signal.signal, this, 'updatedProgressState', 'connection_loggedIn');
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
// deferredResult.addCallback(MochiKit.Async.succeed, {result:"done"});
deferredResult.callback();
return deferredResult;

View File

@ -120,7 +120,8 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.DirectLogin, Object, {
'setLabelKeepingBackwardCompatibilityWithBeta': function (aValue) {
return Clipperz.Async.callbacks("DirectLogin.setLabelKeepingBackwardCompatibilityWithBeta", [
MochiKit.Base.method(this, 'setIndexDataForKey', 'label', aValue),
MochiKit.Base.method(this, 'setValue', 'label', aValue)
MochiKit.Base.method(this, 'setValue', 'label', aValue),
MochiKit.Base.partial(MochiKit.Async.succeed, this),
], {trace:false});
},
@ -497,7 +498,8 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.DirectLogin, Object, {
MochiKit.Base.method(this, 'updateFormValuesAfterChangingBookmarkletConfiguration'),
MochiKit.Base.method(this, 'updateBindingsAfterChangingBookmarkletConfiguration'),
MochiKit.Base.noop
// MochiKit.Base.noop
MochiKit.Base.partial(MochiKit.Async.succeed, this),
], {trace:false});
},
@ -607,6 +609,20 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.DirectLogin, Object, {
], {trace:false});
},
'setBindings': function (someBindings, originalFields) {
var self = this;
return Clipperz.Async.callbacks("DirectLogin.setBindings", [
function () {
return MochiKit.Base.map(function (aBindingInfo) {
return self.bindFormFieldWithLabelToRecordFieldWithLabel(aBindingInfo[0], originalFields[aBindingInfo[1]]['label']);
}, MochiKit.Base.zip(MochiKit.Base.keys(someBindings), MochiKit.Base.values(someBindings)));
},
Clipperz.Async.collectAll,
MochiKit.Base.partial(MochiKit.Async.succeed, this),
], {trace:false});
},
//-------------------------------------------------------------------------
/*
'bindingValues': function () {

View File

@ -72,7 +72,7 @@ Clipperz.PM.DataModel.EncryptedRemoteObject = function(args) {
//
// getRemoteData
// unpackRemoteData
// getDecryptData [encryptedDataKeypath, encryptedVersionKeypath]
// getDecryptedData [encryptedDataKeypath, encryptedVersionKeypath]
// unpackData
//
//

View File

@ -31,18 +31,13 @@ if (typeof(Clipperz.PM.DataModel) == 'undefined') { Clipperz.PM.DataModel = {};
Clipperz.PM.DataModel.OneTimePassword = function(args) {
args = args || {};
// this._user = args['user'];
this._username = args['username'];
this._passphraseCallback = args['passphraseCallback'];
this._reference = args['reference'] || Clipperz.PM.Crypto.randomKey();
this._password = args['password'];
this._passwordValue = Clipperz.PM.DataModel.OneTimePassword.normalizedOneTimePassword(args['password']);
this._creationDate = args['created'] ? Clipperz.PM.Date.parseDateWithUTCFormat(args['created']) : new Date();
this._usageDate = args['used'] ? Clipperz.PM.Date.parseDateWithUTCFormat(args['used']) : null;
this._status = args['status'] || 'ACTIVE'; // 'REQUESTED', 'USED', 'DISABLED'
this._connectionInfo= null;
this._key = null;
this._keyChecksum = null;
this._label = args['label'] || "";
this._usageDate = args['usageDate'] || null; // Usage date is stored when the client is sure that the otp was used
return this;
}
@ -52,19 +47,35 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
'toString': function() {
return "Clipperz.PM.DataModel.OneTimePassword";
},
/*
//-------------------------------------------------------------------------
'user': function() {
return this._user;
'username': function() {
return this._username;
},
//-------------------------------------------------------------------------
'passphraseCallback': function () {
return this._passphraseCallback;
},
'setPassphraseCallback': function(aPassphraseCallback) {
this._passphraseCallback = aPassphraseCallback;
},
'password': function() {
return this._password;
},
'label': function() {
return this._label;
},
'usageDate': function() {
return this._usageDate;
},
'setUsageDate': function(aDate) {
this._usageDate = aDate;
},
//-------------------------------------------------------------------------
'passwordValue': function() {
@ -73,12 +84,6 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
//-------------------------------------------------------------------------
'creationDate': function() {
return this._creationDate;
},
//-------------------------------------------------------------------------
'reference': function() {
return this._reference;
},
@ -86,51 +91,18 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
//-------------------------------------------------------------------------
'key': function() {
if (this._key == null) {
this._key = Clipperz.PM.DataModel.OneTimePassword.computeKeyWithUsernameAndPassword(this.user().username(), this.passwordValue());
}
return this._key;
return Clipperz.PM.DataModel.OneTimePassword.computeKeyWithPassword(this.passwordValue());
},
//-------------------------------------------------------------------------
'keyChecksum': function() {
if (this._keyChecksum == null) {
this._keyChecksum = Clipperz.PM.DataModel.OneTimePassword.computeKeyChecksumWithUsernameAndPassword(this.user().username(), this.passwordValue());
}
return this._keyChecksum;
},
*/
//-------------------------------------------------------------------------
'status': function() {
return this._status;
},
'setStatus': function(aValue) {
this._status = aValue;
},
//-------------------------------------------------------------------------
/*
'serializedData': function() {
var result;
result = {
'password': this.password(),
'created': this.creationDate() ? Clipperz.PM.Date.formatDateWithUTCFormat(this.creationDate()) : null,
'used': this.usageDate() ? Clipperz.PM.Date.formatDateWithUTCFormat(this.usageDate()) : null,
'status': this.status()
};
return result;
return Clipperz.PM.DataModel.OneTimePassword.computeKeyChecksumWithUsernameAndPassword(this.username(), this.passwordValue());
},
//-------------------------------------------------------------------------
'packedPassphrase': function() {
'packedPassphrase': function(aPassphrase) {
var result;
var packedPassphrase;
var encodedPassphrase;
@ -140,13 +112,9 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
getRandomBytes = MochiKit.Base.method(Clipperz.Crypto.PRNG.defaultRandomGenerator(), 'getRandomBytes');
encodedPassphrase = new Clipperz.ByteArray(this.user().passphrase()).toBase64String();
//Clipperz.logDebug("--- encodedPassphrase.length: " + encodedPassphrase.length);
encodedPassphrase = new Clipperz.ByteArray(aPassphrase).toBase64String();
prefixPadding = getRandomBytes(getRandomBytes(1).byteAtIndex(0)).toBase64String();
//Clipperz.logDebug("--- prefixPadding.length: " + prefixPadding.length);
suffixPadding = getRandomBytes((500 - prefixPadding.length - encodedPassphrase.length) * 6 / 8).toBase64String();
//Clipperz.logDebug("--- suffixPadding.length: " + suffixPadding.length);
//Clipperz.logDebug("--- total.length: " + (prefixPadding.length + encodedPassphrase.length + suffixPadding.length));
packedPassphrase = {
'prefix': prefixPadding,
@ -154,18 +122,19 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
'suffix': suffixPadding
};
// result = Clipperz.Base.serializeJSON(packedPassphrase);
result = packedPassphrase;
//Clipperz.logDebug("===== OTP packedPassprase: [" + result.length + "]" + result);
//Clipperz.logDebug("<<< OneTimePassword.packedPassphrase");
return result;
},
//-------------------------------------------------------------------------
'encryptedPackedPassphrase': function() {
return Clipperz.PM.Crypto.deferredEncryptWithCurrentVersion(this.passwordValue(), this.packedPassphrase())
'encryptedPackedPassphrase': function(aPassphrase) {
return Clipperz.PM.Crypto.deferredEncrypt({
'key': this.passwordValue(),
'value': this.packedPassphrase(aPassphrase),
'version': Clipperz.PM.Crypto.encryptingFunctions.currentVersion
})
},
//-------------------------------------------------------------------------
@ -174,8 +143,6 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
var deferredResult;
var result;
//Clipperz.logDebug(">>> OneTimePassword.encryptedData");
//Clipperz.logDebug("--- OneTimePassword.encryptedData - id: " + this.reference());
result = {
'reference': this.reference(),
'key': this.key(),
@ -183,116 +150,26 @@ Clipperz.PM.DataModel.OneTimePassword.prototype = MochiKit.Base.update(null, {
'data': "",
'version': Clipperz.PM.Crypto.encryptingFunctions.currentVersion
}
//Clipperz.logDebug("--- OneTimePassword.encryptedData - 2: " + Clipperz.Base.serializeJSON(result));
deferredResult = new MochiKit.Async.Deferred();
//Clipperz.logDebug("--- OneTimePassword.encryptedData - 3");
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.encryptedData - 1: " + res); return res;});
//# deferredResult.addCallback(Clipperz.PM.Crypto.deferredEncryptWithCurrentVersion, this.passwordValue(), this.packedPassphrase());
deferredResult = new Clipperz.Async.Deferred("OneTimePassword.encryptedData", {trace: false});
deferredResult.addCallback(this.passphraseCallback());
deferredResult.addCallback(MochiKit.Base.method(this, 'encryptedPackedPassphrase'));
//Clipperz.logDebug("--- OneTimePassword.encryptedData - 4");
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.encryptedData - 2: [" + res.length + "]" + res); return res;});
deferredResult.addCallback(function(aResult, res) {
aResult['data'] = res;
return aResult;
}, result);
//Clipperz.logDebug("--- OneTimePassword.encryptedData - 5");
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.encryptedData - 3: " + Clipperz.Base.serializeJSON(res)); return res;});
deferredResult.callback();
//Clipperz.logDebug("--- OneTimePassword.encryptedData - 6");
return deferredResult;
},
//-------------------------------------------------------------------------
'saveChanges': function() {
var deferredResult;
var result;
//Clipperz.logDebug(">>> OneTimePassword.saveChanges");
result = {};
deferredResult = new MochiKit.Async.Deferred();
deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'updatedProgressState', 'saveOTP_encryptUserData');
deferredResult.addCallback(MochiKit.Base.method(this.user(), 'encryptedData'));
deferredResult.addCallback(function(aResult, res) {
aResult['user'] = res;
return aResult;
}, result);
deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'updatedProgressState', 'saveOTP_encryptOTPData');
deferredResult.addCallback(MochiKit.Base.method(this, 'encryptedData'));
deferredResult.addCallback(function(aResult, res) {
aResult['oneTimePassword'] = res;
return aResult;
}, result);
deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'updatedProgressState', 'saveOTP_sendingData');
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.saveChanges - 1: " + Clipperz.Base.serializeJSON(res)); return res;});
deferredResult.addCallback(MochiKit.Base.method(this.user().connection(), 'message'), 'addNewOneTimePassword');
deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'updatedProgressState', 'saveOTP_updatingInterface');
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.saveChanges - 2: " + res); return res;});
deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'notify', 'OTPUpdated');
deferredResult.addCallback(Clipperz.NotificationCenter.deferredNotification, this, 'oneTimePassword_saveChanges_done', null);
//deferredResult.addBoth(function(res) {Clipperz.logDebug("OneTimePassword.saveChanges - 2: " + res); return res;});
deferredResult.callback();
//Clipperz.logDebug("<<< OneTimePassword.saveChanges");
return deferredResult;
},
//-------------------------------------------------------------------------
'usageDate': function() {
return this._usageDate;
},
'setUsageDate': function(aValue) {
this._usageDate = aValue;
},
//-------------------------------------------------------------------------
'connectionInfo': function() {
return this._connectionInfo;
},
'setConnectionInfo': function(aValue) {
this._connectionInfo = aValue;
},
//-------------------------------------------------------------------------
'isExpired': function() {
return (this.usageDate() != null);
},
//-------------------------------------------------------------------------
'updateStatusWithValues': function(someValues) {
var result;
result = false;
if (someValues['status'] != this.status()) {
result = true;
}
this.setStatus(someValues['status']);
this.setUsageDate(Clipperz.PM.Date.parseDateWithUTCFormat(someValues['requestDate']));
this.setConnectionInfo(someValues['connection']);
return result;
},
*/
//-------------------------------------------------------------------------
__syntaxFix__: "syntax fix"
});
//#############################################################################
Clipperz.PM.DataModel.OneTimePassword.computeKeyWithUsernameAndPassword = function(anUsername, aPassword) {
Clipperz.PM.DataModel.OneTimePassword.computeKeyWithPassword = function(aPassword) {
return Clipperz.Crypto.SHA.sha_d256(new Clipperz.ByteArray(aPassword)).toHexString().substring(2);
}
@ -348,3 +225,32 @@ Clipperz.PM.DataModel.OneTimePassword.normalizedOneTimePassword = function(aPass
};
//#############################################################################
Clipperz.PM.DataModel.OneTimePassword.generateRandomBase32OTPValue = function() {
var randomValue;
var result;
randomValue = Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(160/8);
result = randomValue.toBase32String();
result = result.replace(/.{4}\B/g, '$&' + ' ');
result = result.replace(/(.{4} ){2}/g, '$&' + '- ');
return result;
},
//#############################################################################
Clipperz.PM.DataModel.OneTimePassword.createNewOneTimePassword = function(aUsername, aPassphraseCallback) {
var result;
var password;
password = Clipperz.PM.DataModel.OneTimePassword.generateRandomBase32OTPValue();
result = new Clipperz.PM.DataModel.OneTimePassword({
'username': aUsername,
'passphraseCallback': aPassphraseCallback,
'password': password,
'label': ""
});
return result;
}

View File

@ -175,7 +175,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record.Version.Field, Object, {
deferredResult.addMethod(this, 'actionType');
deferredResult.addCallback(function (aValue) { fieldValues['actionType'] = aValue; });
deferredResult.addMethod(this, 'isHidden');
deferredResult.addCallback(function (aValue) { fieldValues['isHidden'] = aValue; });
deferredResult.addCallback(function (aValue) { fieldValues['hidden'] = aValue; });
deferredResult.addCallback(function () { return fieldValues; });
deferredResult.callback();

View File

@ -327,16 +327,15 @@ console.log("Record.Version.hasPendingChanges");
deferredResult = new Clipperz.Async.Deferred('Record.Version.export', {trace:false});
deferredResult.addMethod(this,'fields');
deferredResult.addCallback(MochiKit.Base.values);
deferredResult.addCallback(MochiKit.Base.map, function(fieldIn) {
deferredResult.addCallback(MochiKit.Base.map, function (fieldIn) {
return fieldIn.content();
});
deferredResult.addCallback(Clipperz.Async.collectAll);
deferredResult.addCallback(function(listIn) {
// return listIn.reduce(function(result, field) {
return MochiKit.Iter.reduce(function(result, field) {
var ref = field.reference;
var ref = field['reference'];
result[ref] = field;
delete result[ref].reference;
delete result[ref]['reference'];
return result;
}, listIn, {});
});

View File

@ -1128,7 +1128,8 @@ console.log("Record.hasPendingChanges RESULT", result);
Clipperz.Async.collectAll,
MochiKit.Base.method(aRecord, 'directLogins'), MochiKit.Base.values,
//function (aValue) { console.log("-> DirectLogin Values", aValue); return aValue; },
//function (aValue) { console.log("-> SETUP WITH RECORD: DirectLogin Values", aValue); return aValue; },
// TODO: possibly broken implementation of direct login cloning
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'addDirectLogin')),
//function (aValue) { console.log("-> DirectLogin Values", aValue); return aValue; },
// Clipperz.Async.collectAll,
@ -1137,15 +1138,24 @@ console.log("Record.hasPendingChanges RESULT", result);
], {trace:false});
},
'setUpWithJSON': function(data) {
'directLoginWithJsonData': function (someData) {
var result;
result = new Clipperz.PM.DataModel.DirectLogin({'record': this});
return result;
},
'setUpWithJSON': function(data, labelPostfix) {
return Clipperz.Async.callbacks("Record.setUpWithJSON", [
// TODO: proper tag handling
MochiKit.Base.method(this,'setLabel',data.label),
MochiKit.Base.method(this,'setNotes',data.data.notes),
MochiKit.Base.method(this,'setLabel', data['label'] + ((labelPostfix) ? labelPostfix : '')),
MochiKit.Base.method(this,'setNotes', data['data']['notes']),
// TODO: check whether fields' order is kept or not
function(){ return MochiKit.Base.values(data.currentVersion.fields); },
MochiKit.Base.partial(MochiKit.Base.map,MochiKit.Base.method(this, 'addField')),
Clipperz.Async.collectAll
MochiKit.Base.partial(MochiKit.Base.values, data['currentVersion']['fields']),
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, 'addField')),
Clipperz.Async.collectAll,
MochiKit.Base.partial(MochiKit.Async.succeed, this),
], {trace:false});
},
@ -1174,12 +1184,12 @@ console.log("Record.hasPendingChanges RESULT", result);
var label;
var data;
var currentVersion;
var directLogins;
// var directLogins;
var currentVersionObject;
data = {};
currentVersion = {};
directLogins = {};
// directLogins = {};
deferredResult = new Clipperz.Async.Deferred('Record.export', {trace:false});
deferredResult.addMethod(this, 'getCurrentRecordVersion');
deferredResult.addCallback(function(recordVersionIn) { currentVersionObject = recordVersionIn; })
@ -1211,7 +1221,6 @@ console.log("Record.hasPendingChanges RESULT", result);
__syntaxFix__: "syntax fix"
});
Clipperz.PM.DataModel.Record.defaultCardInfo = {
'_rowObject': MochiKit.Async.succeed,
'_reference': MochiKit.Base.methodcaller('reference'),
@ -1277,3 +1286,14 @@ Clipperz.PM.DataModel.Record.extractTagsFromFullLabel = function (aLabel) {
return result;
};
Clipperz.PM.DataModel.Record.labelContainsTag = function (aLabel, aTag) {
return MochiKit.Iter.some(
MochiKit.Base.keys(Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(aLabel)),
MochiKit.Base.partial(MochiKit.Base.operator.eq, aTag)
);
}
Clipperz.PM.DataModel.Record.labelContainsArchiveTag = function (aLabel) {
return Clipperz.PM.DataModel.Record.labelContainsTag(aLabel, Clipperz.PM.DataModel.Record.archivedTag);
}

View File

@ -31,7 +31,13 @@ if (typeof(Clipperz.PM.DataModel.User.Header) == 'undefined') { Clipperz.PM.Data
Clipperz.PM.DataModel.User.Header.OneTimePasswords = function(args) {
Clipperz.PM.DataModel.User.Header.OneTimePasswords.superclass.constructor.apply(this, arguments);
// TODO: there are still method calls around passing these values: should be cleared...
this._connection = args['connection'];
this._username = args['username'];
this._passphraseCallback = args['retrieveKeyFunction'];
this._oneTimePasswords = null;
this._oneTimePasswordsDetails = null;
return this;
}
@ -45,43 +51,30 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User.Header.OneTimePasswords, Clipper
},
//-------------------------------------------------------------------------
/*
'packData': function (someData) { // ++
var result;
result = Clipperz.PM.DataModel.User.Header.OneTimePasswords.superclass.packData.apply(this, arguments);
return result;
'connection': function() {
return this._connection;
},
*/
//-------------------------------------------------------------------------
/*
'packRemoteData': function (someData) {
var result;
result = Clipperz.PM.DataModel.User.Header.OneTimePasswords.superclass.packRemoteData.apply(this, arguments);
return result;
'username': function() {
return this._username;
},
*/
//-------------------------------------------------------------------------
/*
'prepareRemoteDataWithKey': function (aKey) {
var result;
result = Clipperz.PM.DataModel.User.Header.OneTimePasswords.superclass.prepareRemoteDataWithKey.apply(this, arguments);
return result;
'passphraseCallback': function() {
return this._passphraseCallback;
},
*/
//=========================================================================
'oneTimePasswords': function () {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("User.Header.OneTimePasswords.oneTimePasswords", {trace:false});
// TODO: change with transient state
// Also, OTPs created here don't store username, making it impossible to generate the key checksum (shouldn't be used anywhere, but probably the design should be changed)
if (this._oneTimePasswords == null) {
deferredResult.addMethod(this, 'values')
deferredResult.addMethod(this, 'values');
deferredResult.addCallback(MochiKit.Base.bind(function (someData) {
var otpKey;
@ -93,6 +86,9 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User.Header.OneTimePasswords, Clipper
otpParameters = Clipperz.Base.deepClone(someData[otpKey]);
otpParameters['reference'] = otpKey;
otpParameters['username'] = this.username();
otpParameters['passphraseCallback'] = this.passphraseCallback();
otpParameters['usageDate'] = someData[otpKey]['usageDate'] || null;
otp = new Clipperz.PM.DataModel.OneTimePassword(otpParameters);
this._oneTimePasswords[otpKey] = otp;
@ -109,6 +105,182 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User.Header.OneTimePasswords, Clipper
return deferredResult;
},
'oneTimePasswordsDetails': function() {
if (this._oneTimePasswordsDetails) {
return MochiKit.Async.succeed(this._oneTimePasswordsDetails);
} else {
return Clipperz.Async.callbacks("User.oneTimePasswordsDetails", [
MochiKit.Base.method(this.connection(), 'message', 'getOneTimePasswordsDetails'),
MochiKit.Base.bind(function(someData) {
this._oneTimePasswordsDetails = someData;
return someData;
}, this)
], {trace:false});
}
},
//=========================================================================
'getReferenceFromKey': function(aKey) {
return Clipperz.Async.callbacks("User.Header.OneTimePasswords.getReferenceFromKey", [
MochiKit.Base.method(this, 'values'),
function(someValues) {
var result;
var normalizedOTP;
var i;
result = null;
for (i in someValues) {
normalizedOTP = Clipperz.PM.DataModel.OneTimePassword.normalizedOneTimePassword(someValues[i]['password']);
if (Clipperz.PM.DataModel.OneTimePassword.computeKeyWithPassword(normalizedOTP) == aKey) {
result = i;
}
}
return result;
}
], {trace:false});
},
//=========================================================================
'createNewOTP': function (aUsername, aPassphraseCallback) {
var newOneTimePassword;
newOneTimePassword = Clipperz.PM.DataModel.OneTimePassword.createNewOneTimePassword(aUsername, aPassphraseCallback);
// TODO: this is deferred --> change everything to deferred
// TestData include 'created' and 'status'
this.setValue(newOneTimePassword.reference(), {
// 'created': newOneTimePassword.creationDate().toString(), // won't work: creation date is no more stored in OTP
'password': newOneTimePassword.password(),
'label': newOneTimePassword.label()
// 'status': newOneTimePassword.status()
});
this._oneTimePasswords = null;
this._oneTimePasswordsDetails = null;
return newOneTimePassword;
},
//.........................................................................
'deleteOTPs': function (aList) {
this._oneTimePasswords = null;
this._oneTimePasswordsDetails = null;
return Clipperz.Async.callbacks("User.Header.OneTimePasswords.deleteOTPs", [
MochiKit.Base.method(this, 'values'),
MochiKit.Base.keys,
MochiKit.Base.bind(function(someKeys) {
var result;
result = [];
MochiKit.Base.map(MochiKit.Base.bind(function(aList, aKey) {
if (aList.indexOf(aKey) >= 0) {
this.removeValue(aKey);
} else {
result.push(aKey);
}
}, this, aList), someKeys);
return result; // Return a list of references of the remaining OTPs, needed for the 'updateOneTimePasswords' message
// Maybe this logic should be moved to another method
}, this),
], {trace:false});
},
//.........................................................................
'changeOTPLabel': function (aReference, aLabel) {
this._oneTimePasswords = null;
return Clipperz.Async.callbacks("User.Header.OneTimePasswords.changeOTPLabel", [
MochiKit.Base.method(this, 'getValue', aReference),
function(aValue) {
aValue['label'] = aLabel;
return aValue;
},
MochiKit.Base.method(this, 'setValue', aReference)
], {trace:false});
},
//.........................................................................
'markOTPAsUsed': function(aKey) {
var reference;
this._oneTimePasswords = null;
return Clipperz.Async.callbacks("User.Header.OneTimePasswords.markOTPAsUsed", [
MochiKit.Base.method(this, 'getReferenceFromKey', aKey),
function(aReference) {
reference = aReference;
return aReference;
},
MochiKit.Base.method(this, 'getValue'),
MochiKit.Base.bind(function(aValue) {
if (aValue) {
aValue['usageDate'] = new Date().toString();
this.setValue(reference, aValue);
}
}, this)
], {trace:false});
},
//=========================================================================
'getEncryptedOTPData': function(aPassphraseCallback) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("User.Header.OneTimePasswords.getEncryptedOTPData", {trace:false});
deferredResult.collectResults({
'oneTimePasswords': MochiKit.Base.method(this, 'oneTimePasswords'),
'oneTimePasswordsDetails': MochiKit.Base.method(this, 'oneTimePasswordsDetails')
});
deferredResult.addCallback(function (someData) {
var result;
var otpFilteredList;
var i;
var otpList = MochiKit.Base.values(someData['oneTimePasswords']);
otpFilteredList = MochiKit.Base.filter(function (aOTP) {
return (someData['oneTimePasswordsDetails'][aOTP.reference()]
&& someData['oneTimePasswordsDetails'][aOTP.reference()]['status'] == 'ACTIVE'
&& ! someData['oneTimePasswords'][aOTP.reference()].usageDate()
);
}, otpList);
result = MochiKit.Base.map(function (aOTP) {
aOTP.setPassphraseCallback(aPassphraseCallback);
return aOTP.encryptedData();
}, otpFilteredList);
return result;
});
deferredResult.addCallback(Clipperz.Async.collectAll);
deferredResult.addCallback(function (someData) {
var result;
var i;
result = {};
for (i in someData) {
result[someData[i].reference] = someData[i];
}
return result;
});
deferredResult.callback();
return deferredResult;
},
//=========================================================================
__syntaxFix__: "syntax fix"
});

View File

@ -59,6 +59,8 @@ Clipperz.PM.DataModel.User = function (args) {
'__syntaxFix__': 'syntax fix'
};
this._usedOTP = null;
return this;
}
@ -80,6 +82,40 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
//-------------------------------------------------------------------------
'setUsedOTP': function(aOTP) {
this._usedOTP = aOTP;
return aOTP;
},
'resetUsedOTP': function(aOTP) {
this._usedOTP = null;
},
'markUsedOTP': function(aOTP) {
var result;
var oneTimePasswordKey;
if (this._usedOTP) {
oneTimePasswordKey = Clipperz.PM.DataModel.OneTimePassword.computeKeyWithPassword(
Clipperz.PM.DataModel.OneTimePassword.normalizedOneTimePassword(this._usedOTP)
);
result = Clipperz.Async.callbacks("User.markUsedOTP", [ // NOTE: fired also when passphrase looks exactly like OTP
MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
MochiKit.Base.methodcaller('markOTPAsUsed', oneTimePasswordKey),
MochiKit.Base.method(this,'saveChanges'), // Too 'heavy'?
MochiKit.Base.method(this, 'resetUsedOTP')
], {'trace': false});
} else {
result = MochiKit.Async.succeed();
}
return result;
},
//-------------------------------------------------------------------------
// this.setSubscription(new Clipperz.PM.DataModel.User.Subscription(someServerData['subscription']));
'accountInfo': function () {
return this._accountInfo;
@ -138,21 +174,17 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
//-------------------------------------------------------------------------
'getPassphrase': function() {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("User.getPassphrase", {trace:false});
deferredResult.acquireLock(this.deferredLockForSection('passphrase'));
deferredResult.addMethod(this.data(), 'deferredGetOrSet', 'passphrase', this.getPassphraseFunction());
deferredResult.releaseLock(this.deferredLockForSection('passphrase'));
deferredResult.callback();
return deferredResult;
return this._getPassphraseFunction();
},
'getPassphraseFunction': function () {
return this._getPassphraseFunction;
},
'setPassphraseFunction': function(aFunction) {
this._getPassphraseFunction = aFunction;
},
//-------------------------------------------------------------------------
'getCredentials': function () {
@ -164,26 +196,44 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
//-------------------------------------------------------------------------
'changePassphrase': function (aNewValue) {
return this.updateCredentials(this.username(), aNewValue);
'changePassphrase': function (aNewValueCallback) {
return this.updateCredentials(this.username(), aNewValueCallback);
},
//.........................................................................
'updateCredentials': function (aUsername, aPassphrase) {
'updateCredentials': function (aUsername, aPassphraseCallback) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("User.updateCredentials", {trace:false});
// deferredResult.addMethod(this, 'getPassphrase');
// deferredResult.setValue('currentPassphrase');
deferredResult.addMethod(this.connection(), 'ping');
deferredResult.addMethod(this, 'setUsername', aUsername)
deferredResult.acquireLock(this.deferredLockForSection('passphrase'));
deferredResult.addMethod(this.data(), 'deferredGetOrSet', 'passphrase', aPassphrase);
deferredResult.releaseLock(this.deferredLockForSection('passphrase'));
// deferredResult.getValue('currentPassphrase');
deferredResult.addMethod(this, 'prepareRemoteDataWithKey', aPassphrase);
deferredResult.addMethod(this.connection(), 'updateCredentials', aUsername, aPassphrase);
deferredResult.collectResults({
'newUsername': MochiKit.Base.partial(MochiKit.Async.succeed, aUsername),
'newPassphrase': aPassphraseCallback,
'user': MochiKit.Base.method(this, 'prepareRemoteDataWithKeyFunction', aPassphraseCallback),
'oneTimePasswords': [
MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
MochiKit.Base.methodcaller('getEncryptedOTPData', aPassphraseCallback),
function (otps) {
var result;
var otpRefs;
var i, c;
result = {};
otpRefs = MochiKit.Base.keys(otps);
c = otpRefs.length;
for (i=0; i<c; i++) {
result[otpRefs[i]] = {}
result[otpRefs[i]]['data'] = otps[otpRefs[i]]['data'];
result[otpRefs[i]]['version'] = otps[otpRefs[i]]['version'];
}
return result;
}
]
});
deferredResult.addMethod(this.connection(), 'updateCredentials');
deferredResult.callback();
return deferredResult;
@ -212,8 +262,10 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
'retrieveKeyFunction': MochiKit.Base.method(this, 'getPassphrase')
}),
'oneTimePasswords': new Clipperz.PM.DataModel.User.Header.OneTimePasswords({
'name': 'preferences',
'retrieveKeyFunction': MochiKit.Base.method(this, 'getPassphrase')
'connection': this.connection(),
'name': 'oneTimePasswords',
'username': this.username(),
'passphraseCallback': MochiKit.Base.method(this, 'getPassphrase')
})
}
};
@ -227,18 +279,10 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("User.registerAsNewAccount", {trace:false});
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'updateProgress', {'extraSteps':3});
deferredResult.addMethod(this, 'initialSetupWithNoData')
deferredResult.addMethod(this, 'getPassphrase');
deferredResult.addMethod(this, 'prepareRemoteDataWithKey');
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
deferredResult.addMethod(this.connection(), 'register');
// deferredResult.addCallback(MochiKit.Base.itemgetter('lock'));
// deferredResult.addMethod(this, 'setServerLockValue');
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'userSuccessfullyRegistered');
// deferredResult.addErrback (MochiKit.Base.method(this, 'handleRegistrationFailure'));
deferredResult.callback();
return deferredResult;
@ -276,18 +320,40 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
'login': function () {
var deferredResult;
var oneTimePasswordReference;
deferredResult = new Clipperz.Async.Deferred("User.login", {trace:false});
deferredResult.addMethod(this, 'getPassphrase');
deferredResult.addCallback(Clipperz.PM.DataModel.OneTimePassword.isValidOneTimePasswordValue);
deferredResult.addCallback(Clipperz.Async.deferredIf("Is the passphrase an OTP", [
MochiKit.Base.method(this,'getPassphrase'),
MochiKit.Base.method(this,'setUsedOTP'),
MochiKit.Base.method(this, 'getCredentials'),
MochiKit.Base.method(this.connection(), 'redeemOneTimePassword'),
MochiKit.Base.method(this.data(), 'setValue', 'passphrase')
function (aPassphrase) {
return MochiKit.Base.partial(MochiKit.Async.succeed, aPassphrase);
},
MochiKit.Base.method(this, 'setPassphraseFunction')
], []));
deferredResult.addErrback(MochiKit.Base.method(this, 'getPassphrase'));
deferredResult.addBoth(MochiKit.Base.method(this, 'loginWithPassphrase'));
deferredResult.addBoth(MochiKit.Base.method(this, 'resetUsedOTP'));
deferredResult.callback();
return deferredResult;
},
'loginWithPassphrase': function () {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("User.loginWithPassphrase", {trace:false});
deferredResult.addMethod(this, 'getPassphrase');
deferredResult.addMethod(this.connection(), 'login', false);
deferredResult.addMethod(this, 'setupAccountInfo');
deferredResult.addMethod(this, 'markUsedOTP');
deferredResult.addErrback (MochiKit.Base.method(this, 'handleConnectionFallback'));
deferredResult.callback();
@ -300,21 +366,18 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
'handleConnectionFallback': function(aValue) {
var result;
//console.log("USER - handleConnectionFallback", aValue, aValue['isPermanent']);
if (aValue instanceof MochiKit.Async.CancelledError) {
result = aValue;
} else if ((aValue['isPermanent'] === true) || (Clipperz.PM.Connection.communicationProtocol.fallbackVersions[this.connectionVersion()] == null)) {
result = Clipperz.Async.callbacks("User.handleConnectionFallback - failed", [
MochiKit.Base.method(this.data(), 'removeValue', 'passphrase'),
MochiKit.Base.method(this, 'setConnectionVersion', 'current'),
// MochiKit.Base.partial(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'userLoginFailed'),
// MochiKit.Base.partial(MochiKit.Async.fail, Clipperz.PM.DataModel.User.exception.LoginFailed)
MochiKit.Base.partial(MochiKit.Async.fail, aValue)
], {trace:false});
} else {
this.setConnectionVersion(Clipperz.PM.Connection.communicationProtocol.fallbackVersions[this.connectionVersion()]);
result = new Clipperz.Async.Deferred("User.handleConnectionFallback - retry");
result.addMethod(this, 'login');
result.addMethod(this, 'loginWithPassphrase');
result.callback();
}
@ -324,8 +387,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
//-------------------------------------------------------------------------
'setupAccountInfo': function (aValue) {
//console.log("User.setupAccountInfo", aValue, aValue['accountInfo']);
// this.setLoginInfo(aValue['loginInfo']);
this.setAccountInfo(new Clipperz.PM.DataModel.User.AccountInfo(aValue['accountInfo']));
},
@ -373,8 +434,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
var preferences;
var oneTimePasswords;
// this.setServerLockValue(someServerData['lock']);
headerVersion = this.headerFormatVersion(someServerData['header']);
switch (headerVersion) {
case 'LEGACY':
@ -429,7 +488,9 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
if (typeof(headerData['oneTimePasswords']) != 'undefined') {
oneTimePasswords = new Clipperz.PM.DataModel.User.Header.OneTimePasswords({
'name': 'preferences',
'name': 'oneTimePasswords',
'connection': this.connection(),
'username': this.username(),
'retrieveKeyFunction': MochiKit.Base.method(this, 'getPassphrase'),
'remoteData': {
'data': headerData['oneTimePasswords']['data'],
@ -438,7 +499,9 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
});
} else {
oneTimePasswords = new Clipperz.PM.DataModel.User.Header.OneTimePasswords({
'name': 'preferences',
'name': 'OneTimePasswords',
'connection': this.connection(),
'username': this.username(),
'retrieveKeyFunction': MochiKit.Base.method(this, 'getPassphrase')
});
}
@ -595,7 +658,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
*/
], {trace:false});
},
/*
'filterRecordsInfo': function (someArgs) {
var info = (someArgs.info ? someArgs.info : Clipperz.PM.DataModel.Record.defaultCardInfo);
@ -688,8 +750,47 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
], {trace:false});
},
//.........................................................................
'createNewRecordFromJSON': function(someJSON) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("User.createNewRecordFromJSON", {trace:false});
deferredResult.collectResults({
'recordIndex': MochiKit.Base.method(this, 'getHeaderIndex', 'recordsIndex'),
'newRecord': [
MochiKit.Base.method(this, 'createNewRecord'),
MochiKit.Base.methodcaller('setUpWithJSON', someJSON),
]
});
deferredResult.addCallback(function (someInfo) {
var record = someInfo['newRecord'];
var recordIndex = someInfo['recordIndex'];
return MochiKit.Base.map(function (aDirectLogin) {
var configuration = JSON.stringify({
'page': {'title': aDirectLogin['label']},
'form': aDirectLogin['formData'],
'version': '0.2' // correct?
});
return Clipperz.Async.callbacks("User.createNewRecordFromJSON__inner", [
MochiKit.Base.method(recordIndex, 'createNewDirectLogin', record),
MochiKit.Base.methodcaller('setLabel', aDirectLogin['label']),
MochiKit.Base.methodcaller('setBookmarkletConfiguration', configuration),
MochiKit.Base.methodcaller('setBindings', aDirectLogin['bindingData'], someJSON['currentVersion']['fields']),
], {'trace': false});
}, MochiKit.Base.values(someJSON.data.directLogins));
});
deferredResult.addCallback(Clipperz.Async.collectAll);
deferredResult.callback();
return deferredResult;
},
//-------------------------------------------------------------------------
'cloneRecord': function (aRecord) {
//console.log("USER.cloneRecord", aRecord);
var result;
var user = this;
@ -700,9 +801,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
], [
MochiKit.Base.method(user, 'createNewRecord'),
MochiKit.Base.methodcaller('setUpWithRecord', aRecord),
// function (aValue) { result = aValue; return aValue; },
// MochiKit.Base.method(user, 'saveChanges'),
// function () { return result; }
])
], {trace:false});
},
@ -731,6 +829,62 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
], {trace:false});
},
'getOneTimePasswordsDetails': function() {
return Clipperz.Async.callbacks("User.getOneTimePasswords", [
MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
MochiKit.Base.methodcaller('oneTimePasswordsDetails', this.connection()),
], {trace:false});
},
//-------------------------------------------------------------------------
'createNewOTP': function () {
var messageParameters;
messageParameters = {};
return Clipperz.Async.callbacks("User.createNewOTP", [
MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
MochiKit.Base.methodcaller('createNewOTP', this.username(), MochiKit.Base.method(this, 'getPassphrase')),
MochiKit.Base.methodcaller('encryptedData'),
MochiKit.Base.partial(function(someParameters, someOTPEncryptedData) {
someParameters['oneTimePassword'] = someOTPEncryptedData;
}, messageParameters),
MochiKit.Base.method(this, 'getPassphrase'),
MochiKit.Base.method(this, 'prepareRemoteDataWithKey'),
MochiKit.Base.partial(function(someParameters, someEncryptedRemoteData) {
someParameters['user'] = someEncryptedRemoteData;
}, messageParameters),
MochiKit.Base.method(this.connection(), 'message', 'addNewOneTimePassword', messageParameters)
], {trace:false});
},
'deleteOTPs': function (aList) {
var messageParameters;
messageParameters = {};
return Clipperz.Async.callbacks("User.deleteOTPs", [
MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
MochiKit.Base.methodcaller('deleteOTPs', aList),
MochiKit.Base.partial(function(someParameters, aList) {
someParameters['oneTimePasswords'] = aList
}, messageParameters),
MochiKit.Base.method(this, 'getPassphrase'),
MochiKit.Base.method(this, 'prepareRemoteDataWithKey'),
MochiKit.Base.partial(function(someParameters, someEncryptedRemoteData) {
someParameters['user'] = someEncryptedRemoteData;
}, messageParameters),
MochiKit.Base.method(this.connection(), 'message', 'updateOneTimePasswords', messageParameters)
], {trace:false});
},
'changeOTPLabel': function (aReference, aLabel) {
return Clipperz.Async.callbacks("User.changeOTPLabel", [
MochiKit.Base.method(this, 'getHeaderIndex', 'oneTimePasswords'),
MochiKit.Base.methodcaller('changeOTPLabel', aReference, aLabel),
MochiKit.Base.method(this,'saveChanges') // Too 'heavy'? Should be moved to MainController to prevent glitch in the UI?
], {trace:false});
},
//=========================================================================
'invokeMethodNamedOnHeader': function (aMethodName, aValue) {
@ -804,13 +958,9 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
'revertChanges': function () {
return Clipperz.Async.callbacks("User.revertChanges", [
//function (aValue) { console.log("User.revertChanges - 1"); return aValue; },
MochiKit.Base.method(this, 'invokeMethodNamedOnHeader', 'revertChanges'),
//function (aValue) { console.log("User.revertChanges - 2"); return aValue; },
MochiKit.Base.method(this, 'invokeMethodNamedOnRecords', 'revertChanges'),
//function (aValue) { console.log("User.revertChanges - 3"); return aValue; },
MochiKit.Base.method(this, 'resetTransientState', false),
//function (aValue) { console.log("User.revertChanges - 4"); return aValue; },
], {trace:false});
},
@ -882,6 +1032,13 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
return deferredResult;
},
'prepareRemoteDataWithKeyFunction': function(aKeyFunction) {
return new Clipperz.Async.callbacks("User.prepareRemoteDataWithKeyFunction", [
aKeyFunction,
MochiKit.Base.method(this, 'prepareRemoteDataWithKey')
], {'trace': false})
},
//=========================================================================
'saveChanges': function () {
@ -895,24 +1052,17 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
deferredResult.addMethod(this, 'getHeaderIndex', 'recordsIndex');
deferredResult.addCallback(MochiKit.Base.methodcaller('prepareRemoteDataForChangedRecords'));
deferredResult.addCallback(Clipperz.Async.setItem, messageParameters, 'records');
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
deferredResult.addMethod(this, 'getPassphrase');
deferredResult.addMethod(this, 'prepareRemoteDataWithKey');
deferredResult.addCallback(Clipperz.Async.setItem, messageParameters, 'user');
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
deferredResult.addCallback(MochiKit.Async.succeed, messageParameters);
deferredResult.addMethod(this.connection(), 'message', 'saveChanges');
deferredResult.addCallback(MochiKit.Base.update, this.transientState())
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
deferredResult.addMethod(this, 'commitTransientState');
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'advanceProgress');
// deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'userDataSuccessfullySaved');
deferredResult.addErrbackPass(MochiKit.Base.method(this, 'revertChanges'));
// deferredResult.addErrbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'failureWhileSavingUserData');
deferredResult.callback();

View File

@ -93,6 +93,7 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
//-------------------------------------------------------------------------
// Should this be updated to include OTP field?
'setupWithData': function(someData) {
var deferredResult;
var resultData;
@ -319,13 +320,28 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
result = {};
if (someParameters.message == "connect") {
var userData;
var otpsData, userOTPs;
var randomBytes;
var v;
userData = this.data()['users'][someParameters.parameters.C];
otpsData = (typeof(this.data()['onetimePasswords']) != 'undefined') ? this.data()['onetimePasswords'] : {};
//console.log("Proxy.Offline.DataStore._handshake: otpsData:", otpsData);
userOTPs = {};
MochiKit.Base.map(function(aOTP) {
if (aOTP['user'] == someParameters.parameters.C) {
userOTPs[aOTP['key']] = aOTP;
}
},MochiKit.Base.values(otpsData));
//console.log("Proxy.Offline.DataStore._handshake: userOTPs:", userOTPs);
//console.log("Proxy.Offline.DataStore._handshake(): userOTPs:",userOTPs);
if ((typeof(userData) != 'undefined') && (userData['version'] == someParameters.version)) {
aConnection['userData'] = userData;
aConnection['userOTPs'] = userOTPs;
aConnection['C'] = someParameters.parameters.C;
} else {
aConnection['userData'] = this.data()['users']['catchAllUser'];
@ -495,6 +511,17 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
MochiKit.Base.update(result, aConnection['userData']['records'][someParameters['parameters']['reference']]);
result['reference'] = someParameters['parameters']['reference'];
} else if (someParameters.message == 'getOneTimePasswordsDetails') {
var result = MochiKit.Iter.reduce(function(prev, cur){
prev[cur.reference] = {
'status': cur.status,
'usage_date': cur.usage_date
};
return prev;
}, MochiKit.Base.values(aConnection['userOTPs']), {});
MochiKit.Base.update(result, result);
// console.log("Proxy.Offline.DataStore.getOneTimePasswordsDetails:",result);
//=====================================================================
//
// R E A D - W R I T E M e t h o d s
@ -652,12 +679,60 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
}
//=====================================================================
} else if (someParameters.message == 'addNewOneTimePassword') {
if (this.isReadOnly() == false) {
//console.log("Proxy.Offline.DataStore.addNewOneTimePassword: someParameters:", someParameters);
var otpKey = someParameters['parameters']['oneTimePassword'].key;
if (aConnection['userData']['lock'] != someParameters['parameters']['user']['lock']) {
throw "the lock attribute is not processed correctly"
}
aConnection['userData']['userDetails'] = someParameters['parameters']['user']['header'];
aConnection['userData']['statistics'] = someParameters['parameters']['user']['statistics'];
aConnection['userData']['userDetailsVersion'] = someParameters['parameters']['user']['version'];
aConnection['userOTPs'][otpKey] = someParameters['parameters']['oneTimePassword'];
aConnection['userOTPs'][otpKey]['user'] = aConnection['C'];
aConnection['userOTPs'][otpKey]['status'] = 'ACTIVE';
aConnection['userOTPs'][otpKey]['creation_date'] = new Date().toISOString().substr(0, 19).replace('T', ' '); // Not an elegant way to give the date the same format as the others
aConnection['userOTPs'][otpKey]['request_date'] = "4001-01-01 09:00:00";
aConnection['userOTPs'][otpKey]['usage_date'] = "4001-01-01 09:00:00";
//console.log("Proxy.Offline.DataStore.addNewOneTimePassword: aConnection:", aConnection);
} else {
throw Clipperz.PM.Proxy.Offline.DataStore.exception.ReadOnly;
}
} else if (someParameters.message == 'updateOneTimePasswords') {
if (this.isReadOnly() == false) {
console.log("Proxy.Offline.DataStore.updateOneTimePasswords: someParameters:", someParameters);
if (aConnection['userData']['lock'] != someParameters['parameters']['user']['lock']) {
throw "the lock attribute is not processed correctly"
}
aConnection['userData']['userDetails'] = someParameters['parameters']['user']['header'];
aConnection['userData']['statistics'] = someParameters['parameters']['user']['statistics'];
aConnection['userData']['userDetailsVersion'] = someParameters['parameters']['user']['version'];
console.log("Proxy.Offline.DataStore.updateOneTimePasswords: userOTPs:", aConnection['userOTPs']);
MochiKit.Base.map(function(aOTP) {
if (someParameters['parameters']['oneTimePasswords'].indexOf(aOTP.reference) < 0) {
delete aConnection['userOTPs'][aOTP.key];
}
},MochiKit.Base.values(aConnection['userOTPs']));
} else {
throw Clipperz.PM.Proxy.Offline.DataStore.exception.ReadOnly;
}
//=====================================================================
//
// U N H A N D L E D M e t h o d
//
//=====================================================================
} else {
Clipperz.logError("Clipperz.PM.Proxy.Test.message - unhandled message: " + someParameters.message);
Clipperz.logError("Clipperz.PM.Proxy.Test.message - unhandled message (Proxy.Offline.DataStore): " + someParameters.message);
}
result = {

View File

@ -226,7 +226,9 @@ console.log("DROP"); //, anEvent);
handleKeyDown: function (aField) {
var self = this;
return function (anEvent) {
switch (anEvent.keyCode) {
case 9: // tab
var fieldReferences = MochiKit.Base.map(function (aValue) { return aValue['_reference']}, self.fields());
@ -236,12 +238,14 @@ console.log("DROP"); //, anEvent);
MochiKit.Base.method(aField, 'isEmpty'),
Clipperz.Async.deferredIf('isEmpty',[
], [
MochiKit.Base.method(self, 'addNewField')
MochiKit.Base.method(anEvent, 'preventDefault'),
MochiKit.Base.method(self, 'addNewField'),
// TODO: set the focus to the newly created field
// hints: http://stackoverflow.com/questions/24248234/react-js-set-input-value-from-sibling-component
])
], {trace:false});
}
break;
}
};
@ -393,6 +397,7 @@ console.log("DROP"); //, anEvent);
if (this.state['draggedFieldReference'] != null) {
renderedFields = this.updateRenderedFieldsWithDropArea(renderedFields);
}
return React.DOM.div({'className':'cardFields' /*, 'dropzone':'move'*/}, renderedFields);
},

View File

@ -72,8 +72,10 @@ Clipperz.PM.UI.Components.Cards.TagEditorClass = React.createClass({
//----------------------------------------------------------------------------
addTagValue: function (anEvent) {
this.addTag(anEvent.currentTarget.value);
anEvent.currentTarget.value = "";
if (anEvent.currentTarget.value) {
this.addTag(anEvent.currentTarget.value);
anEvent.currentTarget.value = "";
}
},
handleKeyDown: function (anEvent) {

View File

@ -36,9 +36,9 @@ Clipperz.PM.UI.Components.CheckboxClass = React.createClass({
render: function () {
return React.DOM.div({className:'checkbox', onClick:this.props['eventHandler']}, [
React.DOM.input({name:this.props['id'], id:this.props['id'], value:this.props['id'], type:'checkbox', checked:this.props['checked']}),
React.DOM.label({className:'check', 'for':this.props['id']}),
React.DOM.label({className:'info', 'for':this.props['id']}, "enable local storage")
React.DOM.input({'name':this.props['id'], 'id':this.props['id'], 'value':this.props['id'], 'type':'checkbox', 'checked':this.props['checked']}),
React.DOM.label({'className':'check', 'htmlFor':this.props['id']}),
React.DOM.label({'className':'info', 'htmlFor':this.props['id']}, "enable local storage")
]);
}

View File

@ -59,7 +59,9 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass = React.createClass({
render: function () {
return React.DOM.div({className:'extraFeature devicePIN'}, [
React.DOM.h1({}, "Export"),
React.DOM.div({'className':'header'}, [
React.DOM.h1({}, "Export"),
]),
React.DOM.div({'className': 'content'}, [
React.DOM.ul({}, [
React.DOM.li({}, [
@ -76,7 +78,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass = React.createClass({
React.DOM.div({'className':'description'}, [
React.DOM.p({}, "Download a printer-friendly HTML file that lists the content of all your cards."),
React.DOM.p({}, "This same file also contains all your data in JSON format."),
React.DOM.p({}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file.")
React.DOM.p({'className':'warning'}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file.")
]),
React.DOM.a({'className':'button', 'onClick':this.handleExportLink}, "download HTML+JSON")
]),

View File

@ -24,144 +24,76 @@ refer to http://www.clipperz.com.
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures');
var _steps = ['Input', 'CsvColumns', 'CsvLabels', 'CsvTitles', 'CsvNotes', 'CsvHidden', 'Preview'];
var _stepNames = ['Input', 'Columns', 'Labels', 'Titles', 'Notes','Hidden','Preview'];
Clipperz.PM.UI.Components.ExtraFeatures.DataImportClass = React.createClass({
_steps: _steps,
_stepNames: _stepNames,
_relevantSteps: {
'csv': _steps,
'json': [_steps[0], _steps[6]]
},
getInitialState: function() {
return {
'currentStep': this._steps[0],
'importContext': new Clipperz.PM.UI.ImportContext(),
'nextStepCallback': null,
'error': null
'importContext': new Clipperz.PM.UI.ImportContext(this),
};
},
componentWillUnmount: function () {
this.state['importContext'].release();
this.setState({'importContext': null})
},
//=========================================================================
getStepIndex: function(aStep) {
return this._steps.indexOf(aStep);
importContext: function () {
return this.state.importContext;
},
getStepAfter: function() {
return this._steps[this.getStepIndex(this.state.currentStep) + 1];
},
//=========================================================================
getStepBefore: function() {
return this._steps[this.getStepIndex(this.state.currentStep) - 1];
},
componentWithName: function (aName) {
var result;
var path;
var i, c;
isStepRelevant: function(aStep, aFormat) {
if (!aFormat) {
return true
} else {
return (this._relevantSteps[aFormat].indexOf(aStep) >= 0);
}
},
path = aName.split('.');
//--------------------------------------------------------------------------
goToStep: function(aStep) {
this.setState({
'currentStep': aStep,
'nextStepCallback': null,
'error': null
});
},
handleNextStepOnClick: function() {
if (this.state.nextStepCallback) {
var newImportContext = this.state.nextStepCallback();
if (newImportContext) {
MochiKit.Base.update(this.state.importContext, newImportContext);
if (this.state.currentStep == 'Input' && this.state.importContext.format == 'json') {
this.goToStep('Preview');
} else if (this.state.currentStep == 'Preview') {
this.state.importContext.resetContext();
this.goToStep('Input');
} else {
this.goToStep(this.getStepAfter());
}
} else {
if (this.state.currentStep == "Input") {
this.setState({'error': "unrecognized input format."});
} else {
this.setState({'error': "unknown error."});
}
}
}
},
handleBackOnClick: function() {
if (this.state.importContext.format == 'json' && this.state.currentStep == 'Preview') {
delete this.state.importContext.format;
this.goToStep('Input');
} else if (this.state.currentStep != this._steps[0]) {
this.goToStep(this.getStepBefore());
}
},
setNextStepCallback: function(aFunction) {
this.setState({'nextStepCallback': aFunction});
},
getStepNavbarClass: function(aStep) {
var result;
if (aStep == this.state.currentStep) {
result = 'active';
} else if (this.state.importContext.format == 'json' && (aStep>=1&&aStep<=5) ) {
result = 'disabled';
} else {
result = 'inactive';
result = Clipperz.PM.UI.Components.ExtraFeatures.DataImport;
c = path.length;
for (i=0; i<c; i++) {
result = result[path[i]];
}
return result;
},
//=========================================================================
renderNavbar: function (currentStep) {
return React.DOM.ul({'className': 'stepNavbar' + ' ' + currentStep},
MochiKit.Base.map(MochiKit.Base.bind(function(aStep){
// return React.DOM.li({'className': this.importContext().stepStatus(aStep)}, this.importContext().stepName(aStep));
return React.DOM.li({'className': this.importContext().stepStatus(aStep)}, '.');
}, this),this.importContext().steps())
)
},
render: function () {
return React.DOM.div({className:'extraFeature dataImport'}, [
React.DOM.h1({}, "Import"),
React.DOM.div({'className': 'content'}, [
React.DOM.ul({'className': 'stepNavbar'},
MochiKit.Base.map(MochiKit.Base.bind(function(aStep){
var className;
var currentStep = this.importContext().currentStep().replace('.','_');
if (this.isStepRelevant(aStep,this.state.importContext.format)) {
className = (aStep == this.state.currentStep) ? 'active' : 'inactive';
} else {
className = 'disabled';
}
return React.DOM.li({
'className': className
}, this._stepNames[this.getStepIndex(aStep)]);
}, this),this._steps)
),
new Clipperz.PM.UI.Components.ExtraFeatures.DataImport[this.state.currentStep]({
'importContext': this.state.importContext,
'setNextStepCallback': this.setNextStepCallback,
}),
React.DOM.a({
'className': 'button'+((this.state.currentStep == this._steps[0]) ? ' disabled' : ''),
'onClick': this.handleBackOnClick,
}, "Back"),
React.DOM.a({
'className': 'button'+((! this.state.nextStepCallback) ? ' disabled' : ''),
'onClick': this.handleNextStepOnClick,
}, "Next"),
(this.state.error) ? React.DOM.p({'className': 'error'}, "Error: " + this.state.error) : null
return React.DOM.div({className:'extraFeature dataImport'}, [
React.DOM.div({'className':'header'}, [
React.DOM.h1({}, "Import"),
]),
React.DOM.div({'className': 'content' + ' ' + currentStep + ' ' + this.importContext().inputFormat()}, [
React.DOM.div({'className': 'step' + ' ' + currentStep}, [
new this.componentWithName(this.importContext().currentStep())({
'importContext': this.importContext(),
}),
]),
this.renderNavbar(currentStep),
React.DOM.div({'className': 'buttons' + ' ' + currentStep}, [
React.DOM.a({
'className': 'button back ' + this.importContext().backButtonStatus(),
'onClick': this.importContext().goBackHandler()
}, React.DOM.span({}, "Back")),
React.DOM.a({
'className': 'button next ' + this.importContext().forwardButtonStatus(),
'onClick': this.importContext().goForwardHandler()
}, React.DOM.span({}, "Next"))
])
])
]);
},

View File

@ -0,0 +1,73 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.ColumnsClass = React.createClass({
getInitialState: function() {
return {
'selectedColumns': this.props.importContext.state('csvData.selectedColumns'),
};
},
toggleColumn: function(columnIndex) {
var newSelectedColumns;
newSelectedColumns = this.state['selectedColumns'];
newSelectedColumns[columnIndex] = ! newSelectedColumns[columnIndex];
this.setState({'selectedColumns': newSelectedColumns});
this.props.importContext.setState('csvData.selectedColumns', newSelectedColumns);
},
render: function() {
var importContext = this.props.importContext;
return React.DOM.div({},[
React.DOM.p({}, "Select the columns you want to import."),
React.DOM.table({'className':'csvTable', 'key':'csvTableColumns'},[
React.DOM.thead({}, React.DOM.tr({'className':'columnSelectors', 'key':'csv-colsel'}, MochiKit.Base.map(MochiKit.Base.bind(function (columnIndex) {
var thClasses = {
'title': (columnIndex == importContext.state('csvData.titleIndex')),
'notes': (columnIndex == importContext.state('csvData.notesIndex')),
}
return React.DOM.th({
'key': 'csv-colsel-' + columnIndex,
'className': Clipperz.PM.UI.Components.classNames(thClasses)
}, React.DOM.input({
'key': 'csv-label-input-' + columnIndex,
'type': 'checkbox',
'checked': this.state['selectedColumns'][columnIndex],
'onChange': MochiKit.Base.partial(this.toggleColumn, columnIndex)
}));
}, this), MochiKit.Iter.range(this.state['selectedColumns'].length)))),
this.props.importContext.renderCsvTableBody(false)
])
]);
}
});
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Columns = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.ColumnsClass);

View File

@ -0,0 +1,92 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.HiddenClass = React.createClass({
getInitialState: function() {
return {
'hiddenFields': this.props.importContext.state('csvData.hiddenFields'),
};
},
onChangeCallback: function (columnIndex) {
var newHiddenColumns = this.state['hiddenFields'];
newHiddenColumns[columnIndex] = ! newHiddenColumns[columnIndex];
this.setState({'hiddenFields': newHiddenColumns});
this.props.importContext.setState('csvData.hiddenFields', newHiddenColumns);
},
render: function() {
var importContext = this.props.importContext;
return React.DOM.div({},[
React.DOM.p({}, "Select the fields that should be hidden. (passwords, PINs, ...)"),
React.DOM.table({'className': 'csvTable'},[
React.DOM.thead({},
React.DOM.tr({},
MochiKit.Base.map(MochiKit.Base.bind(function (cellInfo) {
var result;
var columnIndex = cellInfo[0];
var columnValue = cellInfo[1];
var thClasses = {
'title': (columnIndex == importContext.state('csvData.titleIndex')),
'notes': (columnIndex == importContext.state('csvData.notesIndex')),
}
if (importContext.state('csvData.selectedColumns')[columnIndex]) {
result = React.DOM.th({
'key': 'csv-notes-header-' + columnIndex,
'className': Clipperz.PM.UI.Components.classNames(thClasses)
}, [
React.DOM.input({
'type': 'checkbox',
'id': 'csv-notes-input-' + columnIndex,
'key': 'csv-notes-input-' + columnIndex,
'ref': 'csv-notes-input-' + columnIndex,
'checked': this.state['hiddenFields'][columnIndex],
'onChange': MochiKit.Base.partial(this.onChangeCallback, columnIndex),
'disabled': ((columnIndex == importContext.state('csvData.titleIndex')) || (columnIndex == importContext.state('csvData.notesIndex')))
}),
React.DOM.label({'htmlFor': 'csv-notes-input-' + columnIndex}, columnValue),
]);
} else {
result = null;
}
return result;
}, this), Clipperz.Base.zipWithRange(importContext.state('csvData.labels')))
)
),
importContext.renderCsvTableBody(true)
])
]);
}
});
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Hidden = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.HiddenClass);

View File

@ -0,0 +1,121 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.LabelsClass = React.createClass({
getInitialState: function() {
return {
'useFirstRowAsLabels': this.props.importContext.state('csvData.useFirstRowAsLabels'),
'labels': this.props.importContext.state('csvData.labels'),
};
},
//-------------------------------------------------------------------------
updateImportContextState: function () {
this.props.importContext.setState('csvData.useFirstRowAsLabels', this.state['useFirstRowAsLabels']);
this.props.importContext.setState('csvData.labels', this.state['labels']);
},
toggleFirstRow: function() {
var newState;
newState = this.state;
newState['useFirstRowAsLabels'] = ! this.state['useFirstRowAsLabels'];
if (newState['useFirstRowAsLabels']) {
newState['labels'] = MochiKit.Base.map(Clipperz.Base.trim, this.props.importContext.state('csvData.data')[0]);
}
this.setState(newState);
this.updateImportContextState();
},
onChangeLabelCallback: function(columnIndex) {
var newState;
newState = this.state;
newState['labels'][columnIndex] = this.refs['csv-labels-input-' + columnIndex].getDOMNode().value;
this.setState(newState);
this.updateImportContextState();
},
render: function() {
var importContext = this.props.importContext;
return React.DOM.div({},[
React.DOM.p({}, "Set a label for each field in your data. If the first row of the CSV file contains field labels, tick off the checkbox below."),
React.DOM.div({}, [
React.DOM.input({
'id': 'csv-labels-firstrow',
'type': 'checkbox',
'checked': this.state['useFirstRowAsLabels'],
'onChange': this.toggleFirstRow
}),
React.DOM.label({'htmlFor':'csv-labels-firstrow'}, "Use the first row as labels")
]),
React.DOM.table({'className': 'csvTable'},[
React.DOM.thead({},
React.DOM.tr({},
MochiKit.Base.map(MochiKit.Base.bind(function(cellInfo) {
var result;
var columnIndex = cellInfo[0];
var columnValue = cellInfo[1];
var thClasses = {
'title': (columnIndex == importContext.state('csvData.titleIndex')),
'notes': (columnIndex == importContext.state('csvData.notesIndex')),
}
if (importContext.state('csvData.selectedColumns')[columnIndex]) {
result = React.DOM.th({
'key':'csv-labels-header-' + columnIndex,
'className': Clipperz.PM.UI.Components.classNames(thClasses)
}, [
React.DOM.input({
'type': 'text',
'id': 'csv-labels-input-' + columnIndex,
'key': 'csv-labels-input-' + columnIndex,
'ref': 'csv-labels-input-' + columnIndex,
'value': columnValue,
'placeholder': "…",
'onChange': MochiKit.Base.partial(this.onChangeLabelCallback, columnIndex)
})
]);
} else {
result = null;
}
return result;
}, this), Clipperz.Base.zipWithRange(this.state['labels']))
)
),
importContext.renderCsvTableBody(true)
])
]);
}
});
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Labels = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.LabelsClass);

View File

@ -0,0 +1,93 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.NotesClass = React.createClass({
getInitialState: function() {
return {
'notesIndex': this.props.importContext.state('csvData.notesIndex'),
};
},
onChangeCallback: function (columnIndex) {
this.setState({'notesIndex': columnIndex});
this.props.importContext.setState('csvData.notesIndex', columnIndex);
},
render: function() {
var importContext = this.props.importContext;
return React.DOM.div({},[
React.DOM.p({}, "Select the column that represents a \"notes\" field. (optional)"),
React.DOM.input({
'id': 'csv-notes-nonotes',
'type': 'radio',
'checked': ! this.state['notesIndex'],
'onChange': MochiKit.Base.partial(this.onChangeCallback, null)
}),
React.DOM.label({'htmlFor': 'csv-notes-nonotes'}, "\"notes\" field not present"),
React.DOM.table({'className': 'csvTable'},[
React.DOM.thead({},
React.DOM.tr({}, MochiKit.Base.map(MochiKit.Base.bind(function (cellInfo) {
var result;
var columnIndex = cellInfo[0];
var columnValue = cellInfo[1];
var thClasses = {
'title': (columnIndex == importContext.state('csvData.titleIndex')),
'notes': (columnIndex == importContext.state('csvData.notesIndex')),
}
if (importContext.state('csvData.selectedColumns')[columnIndex]) {
result = React.DOM.th({
'key': 'csv-notes-header-' + columnIndex,
'className': Clipperz.PM.UI.Components.classNames(thClasses)
}, [
React.DOM.input({
'type': 'radio',
'id': 'csv-notes-input-' + columnIndex,
'key': 'csv-notes-input-' + columnIndex,
'ref': 'csv-notes-input-' + columnIndex,
'checked': (columnIndex == this.state['notesIndex']),
'onChange': MochiKit.Base.partial(this.onChangeCallback, columnIndex),
'disabled': (columnIndex == importContext.state('csvData.titleIndex'))
}),
React.DOM.label({'htmlFor': 'csv-notes-input-' + columnIndex}, columnValue),
]);
} else {
result = null;
}
return result;
}, this), Clipperz.Base.zipWithRange(this.props.importContext.state('csvData.labels'))))
),
importContext.renderCsvTableBody(true)
])
]);
}
});
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Notes = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.NotesClass);

View File

@ -0,0 +1,84 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.TitlesClass = React.createClass({
getInitialState: function() {
return {
'titleIndex': this.props.importContext.state('csvData.titleIndex'),
};
},
onChangeCallback: function(columnIndex) {
this.setState({'titleIndex': columnIndex});
this.props.importContext.setState('csvData.titleIndex', columnIndex);
},
render: function() {
var importContext = this.props.importContext;
return React.DOM.div({},[
React.DOM.p({}, "Select the column that contains the title of cards you are importing."),
React.DOM.table({'className': 'csvTable'},[
React.DOM.thead({},
React.DOM.tr({}, MochiKit.Base.map(MochiKit.Base.bind(function (cellInfo) {
var result;
var columnIndex = cellInfo[0];
var columnValue = cellInfo[1];
var thClasses = {
'title': (columnIndex == importContext.state('csvData.titleIndex')),
'notes': (columnIndex == importContext.state('csvData.notesIndex')),
}
if (importContext.state('csvData.selectedColumns')[columnIndex]) {
result = React.DOM.th({
'key':'csv-titles-header-' + columnIndex,
'className': Clipperz.PM.UI.Components.classNames(thClasses)
}, [
React.DOM.input({
'type': 'radio',
'id': 'csv-titles-input-' + columnIndex,
'key': 'csv-titles-input-' + columnIndex,
'ref': 'csv-titles-input-' + columnIndex,
'checked': (columnIndex == this.state['titleIndex']),
'onChange': MochiKit.Base.partial(this.onChangeCallback, columnIndex)
}),
React.DOM.label({'htmlFor': 'csv-titles-input-' + columnIndex}, columnValue),
]);
} else {
result = null;
}
return result;
}, this), Clipperz.Base.zipWithRange(importContext.state('csvData.labels'))))
),
importContext.renderCsvTableBody(true)
])
]);
}
});
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Titles = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.TitlesClass);

View File

@ -1,97 +0,0 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumnsClass = React.createClass({
getInitialState: function() {
return {
'selectedColumns': this.props.importContext.selectedColumns
};
},
componentDidMount() {
this.props.setNextStepCallback(this.handleNextStep);
},
//-------------------------------------------------------------------------
handleNextStep: function() {
return this.state;
},
//=========================================================================
toggleColumn: function(columnN) {
var newSelectedColumns;
newSelectedColumns = this.state.selectedColumns;
newSelectedColumns[columnN] = ! newSelectedColumns[columnN];
this.setState({'selectedColumns': newSelectedColumns});
},
render: function() {
//console.log(this.props.importContext);
var columnSelectors;
var rowCount;
var i;
columnSelectors = [];
for (i=0; i<this.props.importContext.nColumns; i++) {
columnSelectors.push( React.DOM.td({'key': 'csv-colsel-' + i}, React.DOM.input({
'type': 'checkbox',
'checked': this.state.selectedColumns[i],
'onChange': MochiKit.Base.partial(this.toggleColumn,i)
}) ) );
}
rowCount = 0;
return React.DOM.div({},[
React.DOM.p({}, "Select the columns you want to import."),
React.DOM.table({'className': 'csvTable'},[
React.DOM.thead({}, React.DOM.tr({'className': 'columnSelectors', 'key': 'csv-colsel'}, columnSelectors)),
React.DOM.tbody({},
MochiKit.Base.map(function(row){
var cellCount;
var result
cellCount = 0;
result = React.DOM.tr({'key': 'csv-row-' + (rowCount++)}, MochiKit.Base.map(function(cell) {
return React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount++)},cell);
}, row));
rowCount++;
return result;
}, this.props.importContext.parsedCsv)
),
])
]);
}
});
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumns = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumnsClass);

View File

@ -1,148 +0,0 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvHiddenClass = React.createClass({
getInitialState: function() {
return {
'hiddenColumns': this.props.importContext.hiddenColumns
};
},
componentDidMount() {
this.props.setNextStepCallback(this.handleNextStep);
},
//-------------------------------------------------------------------------
handleNextStep: function() {
//var importData = this.props.importState.importData;
//var json = this.props.csvToJsonCallback();
//this.props.setImportStateCallback({
// 'importData': importData,
// 'jsonToImport': json,
// 'recordsToImport': MochiKit.Base.map(function(r){return r._importId},json),
// 'currentStep': 'preview',
// 'previousStep': 'csv-hidden'
//})
MochiKit.Base.update(this.props.importContext, this.state);
return true;
},
//=========================================================================
onChangeCallback: function(columnN) {
var newHiddenColumns = this.state.hiddenColumns;
newHiddenColumns[columnN] = ! newHiddenColumns[columnN];
this.setState({'hiddenColumns': newHiddenColumns});
},
render: function() {
var cellCount, rowCount;
var importContext = this.props.importContext;
cellCount = 0;
rowCount = 0;
return React.DOM.div({},[
React.DOM.p({}, "Select the fields that should be hidden. (passwords, PINs, ...)"),
React.DOM.table({'className': 'csvTable'},[
React.DOM.thead({},
React.DOM.tr({},
MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
var result;
var thId = 'csv-notes-header-' + cellCount;
var inputId = 'csv-notes-input-' + cellCount;
if (! importContext.selectedColumns[cellCount]) {
result = null;
} else {
result = React.DOM.th({'key': thId}, [
React.DOM.label({'htmlFor': inputId}, importContext.getCsvLabels()[cellCount]),
React.DOM.input({
'type': 'checkbox',
'id': inputId,
'key': inputId,
'ref': inputId,
'checked': this.state.hiddenColumns[cellCount],
'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount),
'disabled': (cellCount == importContext.titlesColumn || cellCount == importContext.notesColumn)
})
]);
}
cellCount++;
return result;
}, this), importContext.parsedCsv[0])
)
),
React.DOM.tbody({},
MochiKit.Base.map(MochiKit.Base.bind(function(row){
var result;
cellCount = 0;
if (rowCount == 0 && importContext.firstRowAsLabels) {
result = null;
} else {
result = React.DOM.tr({'key': 'csv-row-' + (rowCount)}, MochiKit.Base.map( function(cell) {
var result;
if (importContext.selectedColumns[cellCount]) {
result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
} else{
result = null;
}
cellCount++;
return result;
}, row));
}
rowCount++;
return result;
},this), importContext.parsedCsv)
)
])
]);
}
});
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvHidden = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvHiddenClass);

View File

@ -1,184 +0,0 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvLabelsClass = React.createClass({
getInitialState: function() {
return {
'firstRowAsLabels': this.props.importContext.firstRowAsLabels,
'columnLabels': this.props.importContext.columnLabels,
'columnLabelsFirstrow': this.props.importContext.columnLabelsFirstrow
};
},
componentDidMount() {
this.props.setNextStepCallback((this.isNextDisabled()) ? null : this.handleNextStep);
},
//-------------------------------------------------------------------------
handleNextStep: function() {
return this.state;
},
updateNextStatus: function() {
this.props.setNextStepCallback((! this.isNextDisabled()) ? this.handleNextStep : null);
},
isNextDisabled: function() {
var result;
var importContext = this.props.importContext;
var columnLabels = this.getLabels();
result = false;
for (i in columnLabels) {
result = result || ((columnLabels[i] == '')&&(importContext.selectedColumns[i]));
}
return result;
},
//=========================================================================
getLabels: function() {
return (this.state.firstRowAsLabels) ? this.state.columnLabelsFirstrow : this.state.columnLabels;
},
toggleFirstRow: function() {
var newState;
var cellCount;
newState = this.state;
newState.firstRowAsLabels = ! newState.firstRowAsLabels;
cellCount = 0;
MochiKit.Base.map(function(cell){
newState.columnLabelsFirstrow[cellCount++] = cell;
}, this.props.importContext.parsedCsv[0]);
this.updateNextStatus();
this.setState(newState);
},
onChangeCallback: function(columnN) {
var newState;
newState = this.state;
if (newState.firstRowAsLabels) {
newState.columnLabelsFirstrow[columnN] = this.refs['csv-labels-input-' + columnN].getDOMNode().value;
} else {
newState.columnLabels[columnN] = this.refs['csv-labels-input-' + columnN].getDOMNode().value;
}
this.updateNextStatus();
this.setState(newState);
},
render: function() {
//console.log("labels-render",this.props.importContext);
//return React.DOM.p({}, "labels")
var rowCount, cellCount;
var importContext = this.props.importContext;
var columnLabels = this.getLabels();
rowCount = 0;
cellCount = 0;
return React.DOM.div({},[
React.DOM.p({}, "Set a label for each field in your data. If the first row of the CSV file contains field labels, tick off the checkbox below."),
React.DOM.input({
'id': 'csv-labels-firstrow',
'type': 'checkbox',
'checked': this.state.firstRowAsLabels,
'onChange': this.toggleFirstRow
}),
React.DOM.label({'htmlFor':'csv-labels-firstrow'}, "Use the first row as labels"),
React.DOM.table({'className': 'csvTable'},[
React.DOM.thead({},
React.DOM.tr({},
MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
var result;
if (! importContext.selectedColumns[cellCount]) {
result = null;
} else {
result = React.DOM.th({'key': 'csv-labels-header-' + cellCount}, [
React.DOM.input({
'type': 'text',
'id': 'csv-labels-input-' + cellCount,
'key': 'csv-labels-input-' + cellCount,
'ref': 'csv-labels-input-' + cellCount,
'value': columnLabels[cellCount],
'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount)
})
]);
}
cellCount++;
return result;
}, this), this.props.importContext.parsedCsv[0])
)
),
React.DOM.tbody({},
MochiKit.Base.map(MochiKit.Base.bind(function(row){
var result;
cellCount = 0;
if (rowCount == 0 && this.state.firstRowAsLabels) {
result = null;
} else {
result = React.DOM.tr({'key': 'csv-row-' + (rowCount)}, MochiKit.Base.map( function(cell) {
var result;
if (importContext.selectedColumns[cellCount]) {
result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
} else{
result = null;
}
cellCount++;
return result;
}, row));
}
rowCount++;
return result;
},this), importContext.parsedCsv)
)
])
]);
}
});
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvLabels = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvLabelsClass);

View File

@ -1,138 +0,0 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvNotesClass = React.createClass({
getInitialState: function() {
return {
'notesColumn': this.props.importContext.notesColumn
};
},
componentDidMount() {
this.props.setNextStepCallback(this.handleNextStep);
},
//-------------------------------------------------------------------------
handleNextStep: function() {
return this.state;
},
//=========================================================================
onChangeCallback: function(columnN) {
this.setState({'notesColumn': columnN});
},
render: function() {
var cellCount, rowCount;
var importContext = this.props.importContext;
cellCount = 0;
rowCount = 0;
return React.DOM.div({},[
React.DOM.p({}, "Select the column that represents a \"notes\" field. (optional)"),
React.DOM.input({
'id': 'csv-notes-nonotes',
'type': 'radio',
'checked': ! this.state.notesColumn,
'onChange': MochiKit.Base.partial(this.onChangeCallback, null)
}),
React.DOM.label({'htmlFor': 'csv-notes-nonotes'}, "\"notes\" field not present"),
React.DOM.table({'className': 'csvTable'},[
React.DOM.thead({},
React.DOM.tr({},
MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
var result;
var thId = 'csv-notes-header-' + cellCount;
var inputId = 'csv-notes-input-' + cellCount;
if (! importContext.selectedColumns[cellCount]) {
result = null;
} else {
result = React.DOM.th({'key': thId}, [
React.DOM.label({'htmlFor': inputId}, importContext.getCsvLabels()[cellCount]),
React.DOM.input({
'type': 'radio',
'id': inputId,
'key': inputId,
'ref': inputId,
'checked': cellCount == this.state.notesColumn,
'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount),
'disabled': cellCount == importContext.titlesColumn
})
]);
}
cellCount++;
return result;
}, this), importContext.parsedCsv[0])
)
),
React.DOM.tbody({},
MochiKit.Base.map(MochiKit.Base.bind(function(row){
var result;
cellCount = 0;
if (rowCount == 0 && importContext.firstRowAsLabels) {
result = null;
} else {
result = React.DOM.tr({'key': 'csv-row-' + (rowCount)}, MochiKit.Base.map( function(cell) {
var result;
if (importContext.selectedColumns[cellCount]) {
result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
} else{
result = null;
}
cellCount++;
return result;
}, row));
}
rowCount++;
return result;
},this), importContext.parsedCsv)
)
])
]);
}
});
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvNotes = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvNotesClass);

View File

@ -1,146 +0,0 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvTitlesClass = React.createClass({
getInitialState: function() {
return {
'titlesColumn': this.props.importContext.titlesColumn,
'notesColumn': this.props.importContext.notesColumn
};
},
componentDidMount() {
this.props.setNextStepCallback((this.isNextDisabled()) ? null : this.handleNextStep);
},
//-------------------------------------------------------------------------
handleNextStep: function() {
return this.state;
},
updateNextStatus: function() {
this.props.setNextStepCallback((! this.isNextDisabled()) ? this.handleNextStep : null);
},
isNextDisabled: function() {
return (this.state.titlesColumn != 0 && ! this.state.titlesColumn );
},
//=========================================================================
onChangeCallback: function(columnN) {
var newState = this.state;
if (newState.notesColumn == columnN) {
newState.notesColumn = null;
}
newState.titlesColumn = columnN;
this.updateNextStatus();
this.setState(newState);
},
render: function() {
var rowCount, cellCount;
var importContext = this.props.importContext;
var columnLabels = importContext.getCsvLabels();
rowCount = 0;
cellCount = 0;
return React.DOM.div({},[
React.DOM.p({}, "Select the column that contains titles of the cards you are importing. (mandatory)"),
React.DOM.table({'className': 'csvTable'},[
React.DOM.thead({},
React.DOM.tr({},
MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
var result;
var thId = 'csv-titles-header-' + cellCount;
var inputId = 'csv-titles-input-' + cellCount;
if (! importContext.selectedColumns[cellCount]) {
result = null;
} else {
result = React.DOM.th({'key': thId}, [
React.DOM.label({'htmlFor': inputId}, columnLabels[cellCount]),
React.DOM.input({
'type': 'radio',
'id': inputId,
'key': inputId,
'ref': inputId,
'checked': cellCount == this.state.titlesColumn,
'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount)
})
]);
}
cellCount++;
return result;
}, this), this.props.importContext.parsedCsv[0])
)
),
React.DOM.tbody({},
MochiKit.Base.map(MochiKit.Base.bind(function(row){
var result;
cellCount = 0;
if (rowCount == 0 && importContext.firstRowAsLabels) {
result = null;
} else {
result = React.DOM.tr({'key': 'csv-row-'+(rowCount)}, MochiKit.Base.map( function(cell) {
var result;
if (importContext.selectedColumns[cellCount]) {
result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
} else{
result = null;
}
cellCount++;
return result;
}, row));
}
rowCount++;
return result;
},this), importContext.parsedCsv)
)
])
]);
}
});
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvTitles = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvTitlesClass);

View File

@ -0,0 +1,45 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.ImportClass = React.createClass({
//=========================================================================
importHandler: function (anEvent) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'importCards', this.props.importContext.state('recordsToImport'));
},
render: function() {
return React.DOM.div({}, [
React.DOM.h5({}, "Cards to import: " + this.props.importContext.state('recordsToImport').length + " (of " + this.props.importContext.state('jsonData').length + ")"),
React.DOM.a({'className': 'button import', 'onClick': this.importHandler}, "Import")
]);
},
});
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.Import = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.ImportClass);

View File

@ -28,12 +28,13 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
getInitialState: function() {
return {
'inputString': (this.props.importContext.inputString) ? this.props.importContext.inputString : null,
'format': (this.props.importContext.format) ? this.props.importContext.format : null,
'inputString': this.props.importContext.inputString(),
// 'inputString': (this.props.importContext.inputString) ? this.props.importContext.inputString : null,
// 'format': (this.props.importContext.format) ? this.props.importContext.format : null,
//'parsedInput': (this.props.importContext.parsedInput) ? this.props.importContext.parsedInput : null,
};
},
/*
componentDidMount: function() {
this.updateNextStatus(this.state.inputString);
},
@ -46,12 +47,13 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
var parsedInput;
var inputString = this.refs['input-textarea'].getDOMNode().value.trim();
// this.props.importContext.setData(inputString);
result = {'inputString': inputString};
/*
parsedInput = this.parseJson(inputString);
if (parsedInput) {
MochiKit.Base.update(result,this.props.importContext.getInitialJsonContext(parsedInput));
MochiKit.Base.update(result, this.props.importContext.getInitialJsonContext(parsedInput));
} else {
parsedInput = this.parseCsv(inputString);
if (parsedInput) {
@ -60,12 +62,12 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
result = false;
}
}
* /
return result;
},
updateNextStatus: function(newInputString) {
this.props.setNextStepCallback((newInputString) ? this.handleNextStep : null);
// this.props.setNextStepCallback((newInputString) ? this.handleNextStep : null);
},
//=========================================================================
@ -145,69 +147,87 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
return result;
},
*/
//=========================================================================
handleUploadFiles: function(someFiles) {
updateTextAreaContent: function (aValue, shouldMoveForwardToo) {
var value;
value = this.props.importContext.setInputString(aValue, shouldMoveForwardToo);
this.setState({'inputString': value});
},
handleUploadFiles: function (someFiles) {
var file;
var reader;
if (someFiles.length == 1) {
file = someFiles[0];
reader = new FileReader();
reader = new FileReader();
// Binary files are just thrown in the textarea as weird UTF-8 characters: should we do something about it?
reader.onloadend = MochiKit.Base.bind(function() {
var extractedJson = this.extractJsonFromClipperzExport(reader.result);
var newInputString;
// Binary files are just thrown in the textarea as weird UTF-8 characters: should we do something about it?
reader.onloadend = MochiKit.Base.bind(function() {
/*
var extractedJson = this.extractJsonFromClipperzExport(reader.result);
var newInputString;
if (extractedJson) {
newInputString = extractedJson;
} else {
newInputString = reader.result;
}
if (extractedJson) {
newInputString = extractedJson;
} else {
newInputString = reader.result;
}
this.setState({'inputString': newInputString});
this.updateNextStatus(newInputString);
},this,reader);
this.setState({'inputString': newInputString});
this.updateNextStatus(newInputString);
*/
//console.log("handleUploadFiles", this.props.importContext, this.state, this);
// this.props.importContext.setInputString(reader.result);
this.updateTextAreaContent(reader.result, true);
}, this);
reader.readAsText(file);
reader.readAsText(file);
} else {
// Should this be removed?
alert("Error: expecting a file as input.");
}
},
handleOnDrop: function(e) {
e.preventDefault();
handleOnDrop: function (anEvent) {
anEvent.preventDefault();
this.handleUploadFiles(e.dataTransfer.files)
this.handleUploadFiles(anEvent.dataTransfer.files)
},
handleInputFiles: function(e) {
e.preventDefault();
handleInputFiles: function (anEvent) {
anEvent.preventDefault();
this.handleUploadFiles(e.target.files)
this.handleUploadFiles(anEvent.target.files)
},
handleOnDragOver: function(e) {
handleOnDragOver: function (anEvent) {
// Somehow necessary:
// http://enome.github.io/javascript/2014/03/24/drag-and-drop-with-react-js.html
// https://code.google.com/p/chromium/issues/detail?id=168387
// http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html
e.preventDefault();
anEvent.preventDefault();
},
handleTextareaChange: function() {
var newInputString = this.refs['input-textarea'].getDOMNode().value;
this.setState({'inputString': newInputString});
this.updateNextStatus(newInputString);
handleTextareaChange: function () {
// var newInputString;
//
// newInputString = this.refs['input-textarea'].getDOMNode().value;
// this.setState({'inputString': newInputString});
// this.props.importContext.setInputString(newInputString);
this.updateTextAreaContent(this.refs['input-textarea'].getDOMNode().value, false);
},
//=========================================================================
render: function() {
return React.DOM.div({},[
React.DOM.div({'className':'description'}, [
React.DOM.p({}, "You can import either CSV data, or Clipperz data exported in JSON"),
]),
React.DOM.form({'key':'form', 'className':'importForm' }, [
React.DOM.input({
'type': 'file',
@ -228,7 +248,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
'key':'input-textarea',
'name':'input-textarea',
'ref':'input-textarea',
'placeholder':"Open the JSON file exported from Clipperz in a text editor. Then copy and paste its content here.",
'placeholder':"Copy or type your data here",
'value': this.state.inputString,
'onChange': this.handleTextareaChange,
'onDragOver': this.handleOnDragOver,

View File

@ -27,35 +27,22 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createClass({
getInitialState: function() {
if (this.props.importContext.format == 'csv') {
return this.props.importContext.processCsv()
} else {
return {
'jsonToImport': this.props.importContext.jsonToImport,
'recordsToImport': this.props.importContext.recordsToImport,
}
}
},
var recordsToImport;
componentDidMount() {
this.props.setNextStepCallback(this.handleImport);
},
//-------------------------------------------------------------------------
handleImport: function() {
MochiKit.Base.update(this.props.importContext, this.state);
var filteredImportData = MochiKit.Base.filter(
MochiKit.Base.bind(function(r) {
return this.isRecordToImport(r);
}, this),
this.state.jsonToImport
recordsToImport = MochiKit.Iter.reduce(
function (acc, item) { acc[item['reference']] = item; return acc; },
MochiKit.Base.filter(
function (aRecord) { return !Clipperz.PM.DataModel.Record.labelContainsArchiveTag(aRecord['label']); },
this.props.importContext.state('recordsToImport')
),
{}
);
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'importCards', filteredImportData);
this.props.importContext.setState('recordsToImport', MochiKit.Base.values(recordsToImport));
return true;
return {
'recordsToImport': recordsToImport
};
},
//=========================================================================
@ -65,19 +52,19 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createCl
var recordPosition;
newRecordsToImport = this.state.recordsToImport;
recordPosition = newRecordsToImport.indexOf(record._importId);
if (recordPosition === -1) {
newRecordsToImport.push(record._importId);
if (this.isRecordToImport(record)) {
delete newRecordsToImport[record['reference']];
} else {
newRecordsToImport.splice(recordPosition,1);
newRecordsToImport[record['reference']] = record;
}
this.setState({'recordsToImport': newRecordsToImport});
this.props.importContext.setState('recordsToImport', MochiKit.Base.values(newRecordsToImport));
},
isRecordToImport: function(record) {
return (this.state.recordsToImport.indexOf(record._importId)>=0) ? true : false;
return (MochiKit.Base.keys(this.state.recordsToImport).indexOf(record['reference']) != -1) ? true : false;
},
getTags: function (aTitle) {
@ -87,7 +74,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createCl
var tagObject = Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(aTitle);
tagList = MochiKit.Base.keys(tagObject);
tagList = MochiKit.Base.filter(function(aTag) { return tagObject[aTag] }, tagList);
tagList = MochiKit.Base.filter(function(aTag) { return !Clipperz.PM.DataModel.Record.isSpecialTag(aTag); }, tagList);
if (tagList.length > 0) {
result = React.DOM.ul({'className': 'tagList'},
@ -102,50 +89,44 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createCl
return result;
},
renderCardFields: function(someFields) {
return MochiKit.Base.map(function(key) {
renderCardFields: function (someFields) {
return MochiKit.Base.map(function (key) {
var field = someFields[key];
return [
React.DOM.dt({},field.label),
React.DOM.dd({},field.value),
React.DOM.dt({}, field['label']),
React.DOM.dd({'className': field['actionType'] + (field['hidden'] ? ' password' : '')}, field['value']),
];
} ,MochiKit.Base.keys(someFields));
}, MochiKit.Base.keys(someFields));
},
renderCard: function(aCard) {
var notesParagraph = (aCard.data.notes) ? React.DOM.p({'className': 'notes'}, aCard.data.notes) : null;
return React.DOM.li({'className': 'card'}, [
renderCard: function (aCard) {
var classes;
classes = {
'card': true,
'archived': Clipperz.PM.DataModel.Record.labelContainsArchiveTag(aCard['label'])
}
return React.DOM.li({'className':Clipperz.PM.UI.Components.classNames(classes)}, [
React.DOM.input({
'type': 'checkbox',
'checked': this.isRecordToImport(aCard),
'onChange': MochiKit.Base.partial(this.toggleRecordToImport,aCard)
'onChange': MochiKit.Base.partial(this.toggleRecordToImport, aCard)
}),
React.DOM.h3({}, Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(aCard.label)),
this.getTags(aCard.label),
React.DOM.dl({'className': 'fields'}, this.renderCardFields(aCard.currentVersion.fields)),
notesParagraph
React.DOM.div({'className': 'cardContent'}, [
React.DOM.h3({}, Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(aCard['label'])),
this.getTags(aCard['label']),
React.DOM.dl({'className': 'fields'}, this.renderCardFields(aCard['currentVersion']['fields'])),
(aCard['data']['notes']) ? React.DOM.p({'className': 'notes'}, aCard['data']['notes']) : null
])
]);
},
render: function() {
var result;
if (typeof(this.state.jsonToImport)=='undefined' || !this.state.jsonToImport) {
result = "Error";
} else {
var renderedPreview = React.DOM.ul({},
MochiKit.Base.map(this.renderCard, this.state.jsonToImport)
);
result =
React.DOM.div({'className': 'jsonPreview'}, React.DOM.ul({},
MochiKit.Base.map(this.renderCard, this.state.jsonToImport)
) );
}
return React.DOM.div({},result);
}
return React.DOM.div({'className': 'preview'},
React.DOM.ul({}, MochiKit.Base.map(MochiKit.Base.method(this, 'renderCard'), this.props.importContext.state('jsonData')))
);
},
});

View File

@ -75,17 +75,19 @@ Clipperz.PM.UI.Components.ExtraFeatures.DeleteAccountClass = React.createClass({
render: function () {
return React.DOM.div({className:'extraFeature deleteAccount'}, [
React.DOM.h1({}, "Delete Account"),
React.DOM.div({'className':'header'}, [
React.DOM.h1({}, "Delete Account"),
]),
React.DOM.div({'className': 'content'}, [
React.DOM.form({'key':'form', 'className':'deleteAccountForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleDeleteAccount}, [
React.DOM.div({'key':'fields'},[
React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
React.DOM.input({'key':'username', 'className':this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
React.DOM.label({'key':'passphrase-label', 'autoFocus': 'true', 'htmlFor' :'passphrase'}, "passphrase"),
React.DOM.input({'key':'passphrase', 'className':this.state['passphrase'], 'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase"}),
React.DOM.label({'key':'username-label', 'htmlFor':'name'}, "username"),
React.DOM.input({'key':'username', 'className': this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
React.DOM.label({'key':'passphrase-label', 'autoFocus': 'true', 'htmlFor':'passphrase'}, "passphrase"),
React.DOM.input({'key':'passphrase', 'className': this.state['passphrase'], 'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase"}),
React.DOM.p({}, [
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
React.DOM.span({}, "I understand that all my data will be deleted and that this action is irreversible.")
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'id':'deleteAccountConfirmCheckbox', 'name':'confirm', 'ref':'confirm'}),
React.DOM.label({'htmlFor':'deleteAccountConfirmCheckbox'}, "I understand that all my data will be deleted and that this action is not reversible.")
]),
]),
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableDeleteAccountButton(), 'className':'button'}, "Delete my account")

View File

@ -35,7 +35,9 @@ Clipperz.PM.UI.Components.ExtraFeatures.DevicePINClass = React.createClass({
render: function () {
return React.DOM.div({className:'extraFeature devicePIN'}, [
React.DOM.h1({}, "Device PIN"),
React.DOM.div({'className':'header'}, [
React.DOM.h1({}, "Device PIN"),
]),
React.DOM.div({'className': 'content'}, [
React.DOM.h3({}, this.props['PIN'])
])

View File

@ -0,0 +1,229 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
"use strict";
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures');
Clipperz.PM.UI.Components.ExtraFeatures.OTPClass = React.createClass({
// TODO: add print button!!!!
getInitialState: function() {
return {
// 'selectedOTPs': [],
'labelBeingEdited': null,
'otpLabel': '',
};
},
propTypes: {
},
//=========================================================================
handleNew: function() {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'createNewOTP');
},
handleDelete: function (anOtpReference) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'deleteOTPs', [anOtpReference]);
},
//-------------------------------------------------------------------------
enableOtpLabelEditing: function(anOTP) {
var newState = this.state;
newState['labelBeingEdited'] = anOTP.reference();
newState['otpLabel'] = anOTP.label();
this.setState(newState);
},
updateOtpLabel: function (anOTP, anEvent) {
var newState = this.state;
var newLabel = anEvent.target.value
newState['otpLabel'] = newLabel;
this.setState(newState);
},
handleKeyPressed: function (anOTP, anEvent) {
switch (anEvent.keyCode) {
case 9: // tab
this.handleLabelSave(anOTP);
// TODO: edit label of next OTP
break;
case 13: // enter
this.handleLabelSave(anOTP);
case 27: // escape
this.handleLabelCancel(anOTP);
break;
}
},
handleLabelSave: function (anOTP) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'changeOTPLabel', anOTP.reference(), this.state['otpLabel']);
this.handleLabelCancel()
},
handleLabelCancel: function() {
var newState;
newState = this.state;
newState['labelBeingEdited'] = null;
this.setState(newState);
},
//=========================================================================
handlePrint: function () {
this.printOneTimePasswords();
},
printOneTimePasswords: function () {
var newWindow;
var filteredOtpList = MochiKit.Base.filter(MochiKit.Base.bind(function (anOTP) {
return (this.props.userInfo.otpsDetails[anOTP.reference()]['status'] == 'ACTIVE');
}, this), this.props.userInfo.otpList);
newWindow = window.open("", "");
newWindow.document.write(
'<!DOCTYPE html>' +
'<html lang="en">' +
'<head>' +
'<meta charset="utf-8">' +
'<title>Active One Time Passwords - Clipperz</title>' +
'<style>' +
'li { padding-bottom: 10px; }' +
'li span { display: block; }' +
'span.password { font-family: monospace; font-size: 16pt; padding-bottom: 5px; }' +
'span.label { font-family: sans-serif; font-size: 12pt; }' +
'</style>' +
'</head>' +
'<body>' +
'<ul>' +
MochiKit.Base.map(function (anOTP) {
return '<li>' +
'<span class="password">' + anOTP.password() + '</span>' +
'<span class="label">' + anOTP.label() + '</span>' +
'</li>';
}, filteredOtpList).join('') +
'</ul>' +
'</body>' +
'</html>'
);
newWindow.document.close();
newWindow.focus();
newWindow.print();
newWindow.close();
},
//=========================================================================
renderOtpRows: function() {
var result;
if (this.props.userInfo.otpList) {
result = MochiKit.Base.map(MochiKit.Base.bind(function (anOTP) {
var reference = anOTP.reference();
var otpDetailInfo = this.props.userInfo.otpsDetails[reference];
var labelComponent;
var otpStatusInfo;
var otpClasses;
var optLabel;
otpClasses = {
'otpDetail': true,
};
otpClasses[otpDetailInfo['status']] = true;
if (otpDetailInfo['status'] != 'ACTIVE') {
otpStatusInfo = React.DOM.div({'className':'otpStatusInfo'}, [
React.DOM.span({'className':'otpStatus'}, otpDetailInfo['status']),
React.DOM.span({'className':'requestDate'}, otpDetailInfo['requestDate']),
React.DOM.span({'className':'connectionIp'}, otpDetailInfo['connection']['ip']),
React.DOM.span({'className':'connectionBrowser'}, otpDetailInfo['connection']['browser']),
])
} else {
otpStatusInfo = null;
}
if (reference == this.state.labelBeingEdited) {
labelComponent = React.DOM.input({
'autoFocus':true,
'value':this.state.otpLabel,
'onChange':MochiKit.Base.partial(this.updateOtpLabel, anOTP),
'onKeyDown':MochiKit.Base.partial(this.handleKeyPressed, anOTP),
});
} else {
labelComponent = React.DOM.span({'onClick':MochiKit.Base.partial(this.enableOtpLabelEditing, anOTP)}, (anOTP.label()) ? anOTP.label() : "---")
}
return React.DOM.li({
'key':'otp-' + reference,
'className':Clipperz.PM.UI.Components.classNames(otpClasses)
}, [
React.DOM.div({'className':'otpAction'}, [
React.DOM.a({'onClick':MochiKit.Base.partial(this.handleDelete, reference)}, 'remove OTP'),
]),
React.DOM.div({'className':'otpInfo'}, [
React.DOM.div({'className':'otpPassword'}, anOTP.password()),
React.DOM.div({'className':'otpLabel'}, labelComponent),
otpStatusInfo,
]),
]);
}, this), this.props.userInfo.otpList);
} else {
result = React.DOM.li({}, React.DOM.div({}, "..."));
}
return result;
},
render: function () {
return React.DOM.div({'className':'extraFeature OTP'}, [
React.DOM.div({'className':'header'}, [
React.DOM.h1({}, "One Time Passwords"),
React.DOM.div({'className':'description'}, [
React.DOM.p({}, "A one-time passphrase works like your regular passphrase, but can be used only once. This makes it expecially useful for using it in places where keyloggers may be installed."),
]),
React.DOM.a({'className':'button', 'onClick':this.handlePrint}, "Print")
]),
React.DOM.div({'className':'content'}, [
React.DOM.ul({'className':'otpList'}, this.renderOtpRows()),
React.DOM.div({'className':'actions'}, [
React.DOM.a({'onClick': this.handleNew}, "create new OTP"),
]),
])
]);
},
//=========================================================================
});
Clipperz.PM.UI.Components.ExtraFeatures.OTP = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.OTPClass);

View File

@ -100,7 +100,9 @@ Clipperz.PM.UI.Components.ExtraFeatures.PassphraseClass = React.createClass({
render: function () {
return React.DOM.div({className:'extraFeature passphrase'}, [
React.DOM.h1({}, "Change Passphrase"),
React.DOM.div({'className':'header'}, [
React.DOM.h1({}, "Change Passphrase"),
]),
React.DOM.div({'className': 'content'}, [
React.DOM.form({'key':'form', 'className':'changePassphraseForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleChangePassphrase}, [
React.DOM.div({'key':'fields'},[
@ -117,8 +119,8 @@ Clipperz.PM.UI.Components.ExtraFeatures.PassphraseClass = React.createClass({
React.DOM.input({'key':'confirm-new-passphrase', 'className':this.state['confirm-new-passphrase'], 'type':'password', 'name':'confirm-new-passphrase', 'ref':'confirm-new-passphrase', 'placeholder':"confirm new passphrase"}),
React.DOM.p({}, [
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
React.DOM.span({}, "I understand that Clipperz will not be able to recover a lost passphrase.")
React.DOM.input({'key':'confirm', 'id':'changePassphraseConfirmCheckbox', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
React.DOM.label({'htmlFor':'changePassphraseConfirmCheckbox'}, "I understand that Clipperz will not be able to help me recovering a lost passphrase.")
]),
]),
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableChangePassphraseButton(), 'className':'button'}, "Change passphrase"),

View File

@ -34,16 +34,16 @@ Clipperz.PM.UI.Components.Pages.MainPageClass = React.createClass({
},
propTypes: {
'tags': React.PropTypes.object,
'allTags': React.PropTypes.array,
'messageBox': React.PropTypes.object.isRequired,
'featureSet': React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
'features': React.PropTypes.array.isRequired,
'userInfo': React.PropTypes.object.isRequired,
'accountInfo': React.PropTypes.object.isRequired,
// 'mediaQueryStyle': React.PropTypes.oneOf(['extra-short', 'narrow', 'wide', 'extra-wide']).isRequired,
'tags': React.PropTypes.object,
'allTags': React.PropTypes.array,
'messageBox': React.PropTypes.object.isRequired,
'featureSet': React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
'features': React.PropTypes.array.isRequired,
'userInfo': React.PropTypes.object.isRequired,
'accountInfo': React.PropTypes.object.isRequired,
'style': React.PropTypes.oneOf(Clipperz_PM_UI_availableStyles).isRequired,
// 'cards': React.PropTypes.deferred.isRequired
// 'mediaQueryStyle': React.PropTypes.oneOf(['extra-short', 'narrow', 'wide', 'extra-wide']).isRequired,
// 'cards': React.PropTypes.deferred.isRequired
},
getInitialState: function () {
@ -60,8 +60,7 @@ Clipperz.PM.UI.Components.Pages.MainPageClass = React.createClass({
};
classes[this.props['style']] = true;
//console.log("MAIN PAGE", this.props['showGlobalMask']);
return React.DOM.div({'key':'mainPage', 'className':Clipperz.PM.UI.Components.classNames(classes)/*Clipperz.PM.UI.Components.classNames(classes)*/}, [
return React.DOM.div({'key':'mainPage', 'className':Clipperz.PM.UI.Components.classNames(classes)}, [
this.props['style'] != 'extra-wide' ? Clipperz.PM.UI.Components.Panels.SelectionPanel(this.props) : null,
Clipperz.PM.UI.Components.Panels.MainPanel(this.props),
Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanel(this.props),

View File

@ -26,6 +26,10 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.Panels');
Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
componentDidMount: function () {
MochiKit.Signal.connect(Clipperz.Signal.NotificationCenter, 'closeSettingsPanel', MochiKit.Base.method(this, 'hideExtraFeatureContent'));
},
settingsToggleHandler: function (anEvent) {
//console.log("settingsToggleHandler");
this.hideExtraFeatureContent();
@ -40,6 +44,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
propTypes: {
'accountInfo': React.PropTypes.object.isRequired,
'userInfo': React.PropTypes.object.isRequired
},
getInitialState: function() {
@ -81,6 +86,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
},
extraFeaturesProps: function () {
// console.log("ExtraFeaturesPanel, extraFeaturesProps:",this.props);
return this.props;
},
@ -95,10 +101,16 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
},
showExtraFeatureContent: function (aComponent, aComponentName) {
// console.log("ExtraFeaturesPanel, showExtraFeatureContent")
if (aComponentName == 'OTP') {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'updateOTPListAndDetails');
}
this.setState({
'isFullyOpen':true,
'extraFeatureComponentName': aComponentName,
'extraFeatureContent': aComponent(this.extraFeaturesProps())
// 'extraFeatureContent': aComponent(this.extraFeaturesProps()),
'extraFeatureContentComponent': aComponent // Trying to instantiate the component at every render
});
},
@ -124,16 +136,17 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
React.DOM.ul({'key':'accountUL'}, [
React.DOM.li({'key':'account_1', 'onClick':this.toggleExtraFeatureComponent('Passphrase'), 'className':(this.state['extraFeatureComponentName'] == 'Passphrase') ? 'selected' : ''}, [
React.DOM.h2({'key':'account_1_h2'}, "Passphrase"),
React.DOM.div({'key':'account_1_div'}, [
React.DOM.p({'key':'account_1_p'}, "Change your account passphrase.")
])
// React.DOM.div({'key':'account_1_div'}, [
// React.DOM.p({'key':'account_1_p'}, "Change your account passphrase.")
// ])
]),
React.DOM.li({'key':'account_2'}, [
React.DOM.li({'key':'account_2', 'onClick':this.toggleExtraFeatureComponent('OTP')}, [
React.DOM.h2({}, "One Time Passwords"),
React.DOM.div({}, [
React.DOM.p({}, "")
])
// React.DOM.div({}, [
// React.DOM.p({}, "Manage your OTPs.")
// ])
]),
/*
React.DOM.li({'key':'account_3', 'onClick':this.toggleExtraFeatureComponent('DevicePIN')}, [
React.DOM.h2({}, "Device PIN"),
React.DOM.div({}, [
@ -146,14 +159,16 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
React.DOM.p({}, "")
])
]),
*/
React.DOM.li({'key':'account_5', 'onClick':this.toggleExtraFeatureComponent('DeleteAccount'), 'className':(this.state['extraFeatureComponentName'] == 'DeleteAccount') ? 'selected' : ''}, [
React.DOM.h2({}, "Delete account"),
React.DOM.div({}, [
React.DOM.p({}, "Delete your account for good.")
])
// React.DOM.div({}, [
// React.DOM.p({}, "Delete your account for good.")
// ])
])
])
]),
/*
React.DOM.li({'key':'subscription', 'className':this.state['index']['subscription'] ? 'open' : 'closed'}, [
React.DOM.h1({'onClick':this.toggleIndexState('subscription')}, "Subscription"),
React.DOM.ul({'key':'subscription'}, [
@ -183,6 +198,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
])
])
]),
*/
React.DOM.li({'key':'data', 'className':this.state['index']['data'] ? 'open' : 'closed'}, [
React.DOM.h1({'onClick':this.toggleIndexState('data')}, "Data"),
React.DOM.ul({'key':'data'}, [
@ -195,22 +211,24 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
// ]),
React.DOM.li({'key':'data_2', 'onClick':this.toggleExtraFeatureComponent('DataImport'), 'className':(this.state['extraFeatureComponentName'] == 'DataImport') ? 'selected' : ''}, [
React.DOM.h2({}, "Import"),
React.DOM.div({}, [
React.DOM.p({}, "CSV, JSON, …")
])
// React.DOM.div({}, [
// React.DOM.p({}, "CSV, JSON, …")
// ])
]),
React.DOM.li({'key':'data_3', 'onClick':this.toggleExtraFeatureComponent('DataExport'), 'className':(this.state['extraFeatureComponentName'] == 'DataExport') ? 'selected' : ''}, [
React.DOM.h2({}, "Export"),
React.DOM.div({}, [
React.DOM.p({}, "Offline copy, printable version, JSON, …")
])
// React.DOM.div({}, [
// React.DOM.p({}, "Offline copy, printable version, JSON, …")
// ])
]),
/*
React.DOM.li({'key':'data_4'}, [
React.DOM.h2({}, "Sharing"),
React.DOM.div({}, [
React.DOM.p({}, "Securely share cards with other users")
])
])
*/
])
])
])
@ -227,7 +245,8 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
React.DOM.header({}, [
React.DOM.div({'className':'button', 'onClick':this.hideExtraFeatureContent}, "close")
]),
this.state['extraFeatureContent']
// this.state['extraFeatureContent']
this.state['extraFeatureContentComponent'] ? this.state['extraFeatureContentComponent'](this.props) : null
]);
},

View File

@ -42,6 +42,11 @@ Clipperz.PM.UI.ExportController = function(args) {
"border-bottom: 2px solid black;" +
"}" +
"header p span {" +
// "padding: 0px 4px;" +
"font-weight: bold;" +
"}" +
"h1 {" +
"margin: 0px;" +
"}" +
@ -102,6 +107,7 @@ Clipperz.PM.UI.ExportController = function(args) {
"margin: 0px;" +
"margin-bottom: 5px;" +
"padding-left: 10px;" +
"font-size: 13pt;" +
"}" +
"div > div {" +
@ -110,9 +116,20 @@ Clipperz.PM.UI.ExportController = function(args) {
"padding: 10px;" +
"}" +
"li p, dd.hidden {" +
"white-space: pre-wrap;" +
"word-wrap: break-word;" +
"font-family: monospace;" +
"}" +
"textarea {" +
"width: 100%;" +
"height: 200px;" +
"display: none" +
// "width: 100%;" +
// "height: 200px;" +
"}" +
"a {" +
"color: white;" +
"}" +
"@media print {" +
@ -120,6 +137,10 @@ Clipperz.PM.UI.ExportController = function(args) {
"display: none !important;" +
"}" +
"div > ul > li.archived {" +
"color: #ddd;" +
"}" +
"ul > li {" +
"page-break-inside: avoid;" +
"} " +
@ -175,9 +196,11 @@ MochiKit.Base.update(Clipperz.PM.UI.ExportController.prototype, {
MochiKit.DOM.DIV({},
MochiKit.DOM.DL({},
MochiKit.Base.map(function(key) {
var isHiddenField = jsonCardData.currentVersion.fields[key]['hidden'];
return [
MochiKit.DOM.DT(jsonCardData.currentVersion.fields[key].label),
MochiKit.DOM.DD(jsonCardData.currentVersion.fields[key].value),
MochiKit.DOM.DT({}, jsonCardData.currentVersion.fields[key]['label']),
MochiKit.DOM.DD((isHiddenField ? {'class':'hidden'} : {}), jsonCardData.currentVersion.fields[key]['value']),
];
}, MochiKit.Base.keys(jsonCardData.currentVersion.fields))
)
@ -187,22 +210,30 @@ MochiKit.Base.update(Clipperz.PM.UI.ExportController.prototype, {
},
'renderToHtml': function (jsonData) {
var title;
var style;
var date;
var body;
var title;
var style;
var now;
var dateString;
var timeString
var body;
title = "Clipperz data";
style = this._style;
date = "dd/mm/yyyy";
now = new XDate();
dateString = now.toString("MMM d, yyyy");
timeString = now.toString("HH:mm");
body = MochiKit.DOM.DIV({},
MochiKit.DOM.HEADER({},
MochiKit.DOM.H1({}, "Your data on Clipperz"),
MochiKit.DOM.H5({}, "Export date: " + date),
MochiKit.DOM.H5({}, "Export generated on " + dateString + " at " + timeString),
MochiKit.DOM.DIV({},
MochiKit.DOM.P({}, "Security warning - This file lists the content of all your cards in a printer-friendly format. At the very bottom, the same content is also available in JSON format."),
MochiKit.DOM.P({}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file. We recommend to delete it as soon as it is no longer needed."),
MochiKit.DOM.P({}, "Security warning - This file lists the content of all your cards in a printer-friendly format"),
MochiKit.DOM.P({}, [
"Beware: ",
MochiKit.DOM.SPAN({'class':'warning'}, "all data are unencrypted!"),
" Therefore make sure to properly store and manage this file. We recommend to delete it as soon as it is no longer needed."
]),
MochiKit.DOM.P({}, "If you are going to print its content on paper, store the printout in a safe and private place!"),
MochiKit.DOM.P({}, "And, if you need to access your data when no Internet connection is available, please consider the much safer option of creating an offline copy.")
)
@ -210,31 +241,33 @@ MochiKit.Base.update(Clipperz.PM.UI.ExportController.prototype, {
MochiKit.DOM.UL({}, MochiKit.Base.map(this.renderCardToHtml, jsonData)),
MochiKit.DOM.DIV({},
MochiKit.DOM.H3({}, "JSON content"),
MochiKit.DOM.DIV({},
MochiKit.DOM.P({}, "Instructions on how to use JSON content"),
MochiKit.DOM.P({}, "The JSON version of your data may be useful if you want to move the whole content of your Clipperz account to a new Clipperz account or recover a card that has been accidentally deleted. Just follow these instructions:"),
MochiKit.DOM.OL({},
MochiKit.DOM.LI({}, "Login to your Clipperz account and go to \"Data > Import\"."),
MochiKit.DOM.LI({}, "Select the JSON option."),
MochiKit.DOM.LI({}, "Copy and paste the JSON content in the form.")
),
MochiKit.DOM.P({}, "Of course, the unencrypted JSON content won't be transmitted to the Clipperz server.")
),
// MochiKit.DOM.H3({}, "JSON content"),
// MochiKit.DOM.DIV({},
// MochiKit.DOM.P({}, "Instructions on how to use JSON content"),
// MochiKit.DOM.P({}, "The JSON version of your data may be useful if you want to move the whole content of your Clipperz account to a new Clipperz account or recover a card that has been accidentally deleted. Just follow these instructions:"),
// MochiKit.DOM.OL({},
// MochiKit.DOM.LI({}, "Login to your Clipperz account and go to \"Data > Import\"."),
// MochiKit.DOM.LI({}, "Select the JSON option."),
// MochiKit.DOM.LI({}, "Copy and paste the JSON content in the form.")
// ),
// MochiKit.DOM.P({}, "Of course, the unencrypted JSON content won't be transmitted to the Clipperz server.")
// ),
MochiKit.DOM.TEXTAREA({}, Clipperz.Base.serializeJSON(jsonData)),
MochiKit.DOM.FOOTER({},
MochiKit.DOM.P({},
"This file has been downloaded from clipperz.is, a service by Clipperz Srl. - ",
"This file has been downloaded from ",
MochiKit.DOM.A({'href':'https://clipperz.is'} ,"clipperz.is"),
", a service by Clipperz Srl. - ",
MochiKit.DOM.A({'href':'https://clipperz.is/terms_service/'}, "Terms of service"),
" - ",
MochiKit.DOM.A({'href':'https://clipperz.is/privacy_policy/'}, "Privacy policy")
),
MochiKit.DOM.H4({}, "Clipperz - keep it to yourself")
)
// MochiKit.DOM.H4({}, "Clipperz - keep it to yourself")
)
)
);
return '<html><head><title>' + title + '</title><style type="text/css">' + style + '</style></head><body>' + MochiKit.DOM.toHTML(body) + '</body></html>';
return '<html><head><title>' + title + '</title><style type="text/css">' + style + '</style><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' + MochiKit.DOM.toHTML(body) + '</body></html>';
},
//----------------------------------------------------------------------------

View File

@ -24,123 +24,420 @@ refer to http://www.clipperz.com.
"use strict";
Clipperz.Base.module('Clipperz.PM.UI');
Clipperz.PM.UI.ImportContext = function(args) {
Clipperz.PM.UI.ImportContext = function(anInputComponent) {
this._importComponent = anInputComponent;
this.inputString = null;
this._status = {
'inputString': '',
'isInputStringValid': false,
'inputFormat': 'UNDEFINED',
'currentStep': 'Input',
};
return this;
}
MochiKit.Base.update(Clipperz.PM.UI.ImportContext.prototype, {
'toString': function() {
toString: function() {
return "Clipperz.PM.UI.ImportContext";
},
release: function () {
this._importComponent = null;
},
//=============================================================================
'resetContext': function() {
delete this.inputString;
delete this.format;
delete this.jsonToImport;
delete this.recordsToImport;
ensureStateConsistency: function () {
var csvData;
csvData = this._status['csvData'];
if (csvData != null) {
if (csvData['titleIndex'] == csvData['notesIndex']) {
csvData['notesIndex'] = null;
}
csvData['hiddenFields'][csvData['titleIndex']] = false;
csvData['hiddenFields'][csvData['notesIndex']] = false;
}
},
'getInitialJsonContext': function(aJsonList) {
return {
'format': 'json',
'jsonToImport': aJsonList,
'recordsToImport': aJsonList.map(function(d){return d._importId})
};
updateImportComponent: function () {
this._importComponent.setState({'importContext': this});
},
'getInitialCsvContext': function(aCsvTable) {
var result;
var nColumns;
var defaultSelectedColumns;
var defaultHiddenColumns;
var defaultColumnLabels;
var columnLabelsFirstrow;
var i;
//=============================================================================
nColumns = aCsvTable[0].length;
state: function (aKeyPath) {
var result;
var keys;
var i, c;
defaultSelectedColumns = {};
defaultHiddenColumns = {};
defaultColumnLabels = {};
columnLabelsFirstrow = {};
for (i=0; i<nColumns; i++) {
defaultSelectedColumns[i] = true;
defaultHiddenColumns[i] = false;
defaultColumnLabels[i] = "";
columnLabelsFirstrow[i] = aCsvTable[0][i];
result = this._status;
keys = aKeyPath.split('.');
c = keys.length;
for (i=0; i<c; i++) {
result = result[keys[i]];
}
return {
'format': 'csv',
'parsedCsv': aCsvTable,
'nColumns': nColumns,
'selectedColumns': defaultSelectedColumns,
'firstRowAsLabels': false,
'columnLabels': defaultColumnLabels,
'columnLabelsFirstrow': columnLabelsFirstrow,
'titlesColumn': null,
'notesColumn': null,
'hiddenColumns': defaultHiddenColumns,
};
return result;
},
'getCsvLabels': function() {
return (this.firstRowAsLabels) ? this.columnLabelsFirstrow : this.columnLabels;
setState: function (aKeyPath, aValue) {
var object;
var keys;
var i, c;
object = this._status;
keys = aKeyPath.split('.');
c = keys.length - 1;
for (i=0; i<c; i++) {
object = object[keys[i]];
}
object[keys[c]] = aValue;
this.ensureStateConsistency();
this.updateImportComponent();
},
'processCsv': function() {
var jsonToImport;
var recordsToImport;
var columnLabels = this.getCsvLabels();
//=============================================================================
jsonToImport = [];
stepStatus: function (aStep) {
var result;
for (rowCount=0; rowCount<this.parsedCsv.length; rowCount++) {
var rowCount,cellCount;
if (aStep == this.currentStep()) {
result = 'active';
} else {
result = 'disabled';
}
if (rowCount != 0 || ! this.firstRowAsLabels) {
var record;
return result;
},
record = {};
record._importId = rowCount;
record.label = this.parsedCsv[rowCount][this.titlesColumn];
record.data = {'notes': ""};
record.currentVersion = {'fields': {}};
currentStep: function () {
return this.state('currentStep');
},
for (cellCount=0; cellCount<this.parsedCsv[rowCount].length; cellCount++) {
if (this.selectedColumns[cellCount] && cellCount != this.notesColumn && cellCount != this.titlesColumn) {
var fieldKey = rowCount+"-"+cellCount;
var field = {
'label': columnLabels[cellCount],
'value': this.parsedCsv[rowCount][cellCount],
'hidden': this.hiddenColumns[cellCount]
};
record.currentVersion.fields[fieldKey] = field;
} else if (cellCount == this.notesColumn) {
record.data.notes = this.parsedCsv[rowCount][cellCount];
}
}
setCurrentStep: function (aValue) {
this.setState('currentStep', aValue);
},
jsonToImport.push(record);
currentStepIndex: function () {
return MochiKit.Base.findValue(this.steps(), this.currentStep());
},
steps: function () {
var result;
if (this.inputFormat() == 'JSON') {
result = ['Input', 'Preview', 'Import'];
} else if (this.inputFormat() == 'CSV') {
result = ['Input', 'CSV.Columns', 'CSV.Labels', 'CSV.Titles', 'CSV.Notes', 'CSV.Hidden', 'Preview', 'Import'];
} else {
result = ['Input'];
}
return result;
},
//=============================================================================
inputFormat: function () {
return this.state('inputFormat');
},
setInputFormat: function (aValue) {
this.setState('inputFormat', aValue);
if (aValue == 'UNDEFINED') {
this.setIsInputStringValid(false);
} else {
this.setIsInputStringValid(true);
}
},
//-----------------------------------------------------------------------------
isInputStringValid: function () {
return this.state('isInputStringValid');
},
setIsInputStringValid: function (aValue) {
this.setState('isInputStringValid', aValue);
},
//=============================================================================
showJsonPreview: function (jsonData) {
MochiKit.Async.callLater(0.1, MochiKit.Base.method(this, 'setCurrentStep', 'Preview'));
},
setJsonData: function (someData) {
if (someData != null) {
this.setInputFormat('JSON');
}
this.setState('jsonData', someData);
// TODO: before setting 'recordsToImport', filter 'someData' to remove cards marked as ARCHIVED
this.setState('recordsToImport', someData);
},
enhanceJsonDataWithCardReferences: function (someJsonData) {
return MochiKit.Base.map(function (item) {
item['reference'] = Clipperz.PM.Crypto.randomKey();
item['label'] = "COPY - " + item['label'];
return item;
}, someJsonData);
},
//-----------------------------------------------------------------------------
startCsvWizard: function (csvData) {
MochiKit.Async.callLater(0.1, MochiKit.Base.method(this, 'setCurrentStep', 'CSV.Columns'));
},
setCsvData: function (someData) {
if (someData != null) {
this.setInputFormat('CSV');
this.setState('csvData', {
'data': someData,
'selectedColumns': MochiKit.Base.map(function () { return true; }, someData[0]),
'labels': MochiKit.Base.map(function () { return ""; }, someData[0]),
'useFirstRowAsLabels': false,
'titleIndex': null,
'notesIndex': null,
'hiddenFields': MochiKit.Base.map(function () { return false; }, someData[0]),
});
} else {
this.setState('csvData', null);
}
},
csvFillEmptyCells: function(table) {
var result = [];
var i,j;
var maxColumns = MochiKit.Iter.reduce(function(prev,next) {
return Math.max(prev,next)
}, MochiKit.Base.map(function(row) {return row.length;}, table) );
for (i=0; i<table.length; i++) {
result[i] = [];
for (j=0; j<maxColumns; j++) {
result[i][j] = (typeof(table[i][j]) != "undefined") ? table[i][j] : "";
}
}
if (typeof(this.recordsToImport) == 'undefined') {
recordsToImport = MochiKit.Base.map(function(r){return r._importId},jsonToImport);
} else {
recordsToImport = this.recordsToImport;
return result;
},
//............................................................................
'createJsonDataFromCSV': function(csvData) {
return MochiKit.Base.map(function (row) {
var fields;
fields = MochiKit.Base.map(function (cellInfo) {
return {
'label': csvData['labels'][cellInfo[0]],
'value': cellInfo[1],
'hidden': csvData['hiddenFields'][cellInfo[0]],
}
}, MochiKit.Base.filter(function (cellInfo) {
return ((csvData['titleIndex'] != cellInfo[0]) && (csvData['notesIndex'] != cellInfo[0]) && (csvData['selectedColumns'][cellInfo[0]]));
}, Clipperz.Base.zipWithRange(row))
);
return {
'reference': Clipperz.PM.Crypto.randomKey(),
'label': row[csvData['titleIndex']],
'data': {
'notes': ((csvData['notesIndex'] != null) ? row[csvData['notesIndex']] : "")
},
'currentVersion': {
'fields': MochiKit.Iter.reduce(function (accumulator, field) {
accumulator[Clipperz.PM.Crypto.randomKey()] = field; return accumulator;
}, fields, {})
}
};
}, (csvData['useFirstRowAsLabels']) ? csvData['data'].slice(1) : csvData['data']);
},
//=============================================================================
inputString: function () {
return this.state('inputString');
},
setInputString: function (aValue, isUploadingFile) {
var textarea;
var result;
result = aValue;
this.setInputFormat('UNDEFINED');
this.setJsonData(null);
this.setCsvData(null);
if (isUploadingFile) {
var isExportContent;
isExportContent = new RegExp('.*<textarea>(.*)<\/textarea>.*', 'g');
if (isExportContent.test(aValue)) {
textarea = MochiKit.DOM.TEXTAREA();
textarea.innerHTML = aValue.replace(isExportContent, '$1');
result = textarea.innerHTML;
}
}
return {
'jsonToImport': jsonToImport,
'recordsToImport': recordsToImport
};
try {
var jsonData;
jsonData = JSON.parse(result);
jsonData = this.enhanceJsonDataWithCardReferences(jsonData);
this.setJsonData(jsonData);
if (isUploadingFile == true) {
this.showJsonPreview();
}
} catch(e) {
var parsedCsv;
parsedCsv = Papa.parse(result);
if (parsedCsv.errors.length == 0) {
var csvData;
csvData = this.csvFillEmptyCells(parsedCsv.data);
this.setCsvData(csvData);
if (isUploadingFile == true) {
this.startCsvWizard(csvData);
}
}
}
this.setState('inputString', result);
return result;
},
//=============================================================================
backButtonStatus: function () {
var result;
result = 'DISABLED';
if (this.currentStepIndex() > 0) {
result = 'ENABLED';
}
return result;
},
//.............................................................................
forwardButtonStatus: function () {
var result;
result = 'DISABLED';
if (this.currentStep() == 'Input') {
if (this.isInputStringValid()) {
result = 'ENABLED';
}
} else if (this.currentStep() == 'Preview') {
if (this.state('recordsToImport').length > 0) {
result = 'ENABLED';
}
} else if (this.currentStep() == 'CSV.Columns') {
if (MochiKit.Iter.some(this.state('csvData.selectedColumns'), MochiKit.Base.operator.identity)) {
result = 'ENABLED';
}
} else if (this.currentStep() == 'CSV.Labels') {
var selectedColumns = this.state('csvData.selectedColumns');
if (MochiKit.Iter.every(Clipperz.Base.zipWithRange(this.state('csvData.labels')), function (labelInfo) { return (Clipperz.Base.trim(labelInfo[1]).length > 0) || (selectedColumns[labelInfo[0]] == false)})) {
result = 'ENABLED';
}
} else if (this.currentStep() == 'CSV.Titles') {
if ((this.state('csvData.titleIndex') != null) && (this.state('csvData.selectedColumns')[this.state('csvData.titleIndex')] == true)) {
result = 'ENABLED';
}
} else if (this.currentStep() == 'CSV.Notes') {
result = 'ENABLED';
} else if (this.currentStep() == 'CSV.Hidden') {
result = 'ENABLED';
}
return result;
},
//=============================================================================
goBackHandler: function () {
return MochiKit.Base.bind(function (anEvent) {
if (this.backButtonStatus() == 'ENABLED') {
this.goBack();
}
}, this);
},
goForwardHandler: function () {
return MochiKit.Base.bind(function (anEvent) {
if (this.forwardButtonStatus() == 'ENABLED') {
this.goForward();
}
}, this);
},
//=============================================================================
goBack: function () {
this.setCurrentStep(this.steps()[this.currentStepIndex() - 1]);
},
goForward: function () {
if (this.currentStep() == 'CSV.Hidden') {
var jsonData;
jsonData = this.createJsonDataFromCSV(this.state('csvData'));
this.setState('jsonData', jsonData);
this.setState('recordsToImport', jsonData);
}
this.setCurrentStep(this.steps()[this.currentStepIndex() + 1]);
},
//=============================================================================
renderCsvTableBody: function (hideDeselectedColumns) {
var importContext = this;
return React.DOM.tbody({}, MochiKit.Base.map(function (rowInfo) {
var result;
var rowIndex = rowInfo[0];
var row = rowInfo[1]
result = React.DOM.tr({'key': 'csv-row-' + rowIndex},
MochiKit.Base.map(function (cellInfo) {
var result;
var columnIndex = cellInfo[0];
var columnValue = cellInfo[1];
if (importContext.state('csvData.selectedColumns')[columnIndex] || !hideDeselectedColumns) {
result = React.DOM.td({
'key':'csv-cell-' + rowIndex + '-' + columnIndex,
'className':(importContext.state('csvData.hiddenFields')[columnIndex]) ? 'PASSWORD' : null
}, columnValue);
} else{
result = null;
}
return result;
}, Clipperz.Base.zipWithRange(row))
);
return result;
}, Clipperz.Base.zipWithRange((importContext.state('csvData.useFirstRowAsLabels')) ? importContext.state('csvData.data').slice(1) : importContext.state('csvData.data'))))
},
//=============================================================================

View File

@ -63,6 +63,7 @@ Clipperz.PM.UI.MainController = function() {
this.registerForNotificationCenterEvents([
'doLogin', 'registerNewUser', 'showRegistrationForm', 'goBack',
'changePassphrase', 'deleteAccount',
'updateOTPListAndDetails', 'createNewOTP', 'deleteOTPs', 'changeOTPLabel',
// 'export',
'importCards',
'downloadExport',
@ -122,6 +123,16 @@ MochiKit.Base.update(Clipperz.PM.UI.MainController.prototype, {
},
//=========================================================================
/*
proxy: function () {
return this._proxy;
},
setProxy: function (aValue) {
this._proxy = aValue;
},
*/
//=========================================================================
isOnline: function() {
return navigator.onLine;
@ -246,10 +257,8 @@ console.log("THE BROWSER IS OFFLINE");
var canRegisterNewUsers;
canRegisterNewUsers = Clipperz.PM.Proxy.defaultProxy.canRegisterNewUsers();
//console.log("CAN REGISTER NEW USERS", canRegisterNewUsers);
this.selectInitialProxy();
shouldShowRegistrationForm = parameters['shouldShowRegistrationForm'] && canRegisterNewUsers;
// this.pages()['loginPage'].setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable':canRegisterNewUsers});
this.showLoginForm();
if (shouldShowRegistrationForm) {
@ -262,7 +271,7 @@ console.log("THE BROWSER IS OFFLINE");
//-------------------------------------------------------------------------
checkPassphrase: function (passphraseIn) {
checkPassphrase: function( passphraseIn ) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("MainController.checkPassphrase", {trace: false});
@ -931,13 +940,12 @@ console.log("THE BROWSER IS OFFLINE");
//.........................................................................
userInfo: function() {
userInfo: function () {
var result;
result = {
'checkPassphraseCallback': MochiKit.Base.bind(this.checkPassphrase,this)
};
result = {};
result['checkPassphraseCallback'] = MochiKit.Base.bind(this.checkPassphrase,this);
if (this.user() != null) {
result['username'] = this.user().username();
}
@ -1116,6 +1124,9 @@ console.log("THE BROWSER IS OFFLINE");
toggleSettingsPanel_handler: function (anEvent) {
this._isSettingsPanelOpen = !this._isSettingsPanelOpen;
this.setCloseMaskAction(MochiKit.Base.method(this, 'toggleSettingsPanel_handler'));
if (this._isSettingsPanelOpen == false) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'closeSettingsPanel');
}
this.refreshCurrentPage();
},
@ -1281,7 +1292,7 @@ console.log("THE BROWSER IS OFFLINE");
deferredResult = new Clipperz.Async.Deferred("MainController.changePassphrase_handler", {trace: false});
// deferredResult.addMethod(currentPage, 'setProps', {'showGlobalMask':true});
deferredResult.addMethod(this.overlay(), 'show', "changing …", true);
deferredResult.addMethod(this.user(), 'changePassphrase', newPassphrase);
deferredResult.addMethod(this.user(), 'changePassphrase', getPassphraseDelegate);
deferredResult.addMethod(user, 'login');
deferredResult.addMethod(this, 'setUser', user);
// deferredResult.addMethod(currentPage, 'setProps', {'mode':'view', 'showGlobalMask':false});
@ -1317,19 +1328,18 @@ console.log("THE BROWSER IS OFFLINE");
importCards_handler: function(data) {
return Clipperz.Async.callbacks("MainController.importCards_handler", [
MochiKit.Base.method(this.overlay(), 'show', "importing …", true),
function() { return data; },
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, function(recordData) {
var newRecord;
// I have the feeling this should be done in a more elegant way
return Clipperz.Async.callbacks("MainController.importCards_handler-newRecord", [
MochiKit.Base.method(this.user(), 'createNewRecord'),
function (aValue) {
newRecord = aValue;
return newRecord;
},
MochiKit.Base.methodcaller('setUpWithJSON', recordData),
])
})),
MochiKit.Base.partial(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'toggleSettingsPanel'),
// MochiKit.Base.method(this.pages()[this.currentPage()], 'setProps', {'mode':'view', 'showGlobalMask':false}),
function () { return data; },
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this.user(), 'createNewRecordFromJSON')),
// MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.bind(function (recordData) {
// return Clipperz.Async.callbacks("MainController.importCards_handler-newRecord", [
// MochiKit.Base.method(this.user(), 'createNewRecord'),
// MochiKit.Base.methodcaller('setUpWithJSON', recordData),
// ], {trace:false})
// }, this)),
Clipperz.Async.collectAll,
MochiKit.Base.method(this.user(), 'saveChanges'),
MochiKit.Base.partial(MochiKit.Base.method(this, 'resetRecordsInfo')),
@ -1341,6 +1351,61 @@ console.log("THE BROWSER IS OFFLINE");
//----------------------------------------------------------------------------
updateOTPListAndDetails: function() {
return Clipperz.Async.callbacks("MainController.updateOTPListAndDetails", [
Clipperz.Async.collectResults("User.updateOTPListAndDetails <inner results>", {
'userInfo': MochiKit.Base.method(this, 'userInfo'),
'otpDetails': Clipperz.Async.collectResults("User.updateOTPListAndDetails <otpDetails>", {
'otpList': MochiKit.Base.method(this.user(),'getOneTimePasswords'),
'otpsDetails': MochiKit.Base.method(this.user(),'getOneTimePasswordsDetails'),
}),
}, {trace:false}),
function (someData) {
return MochiKit.Base.update(someData['userInfo'], someData['otpDetails']);
},
MochiKit.Base.bind(function(someUserInfo) {
this.setPageProperties('mainPage', 'userInfo', someUserInfo);
}, this)
], {trace:false});
},
/* Used only one time (the first time the OTP ExtraFeature loads), other times
the list update is triggered by other operations. Maybe the first OTP list retrieval
could be done during init, so that this would not be necessary. */
updateOTPListAndDetails_handler: function () {
return this.updateOTPListAndDetails();
},
createNewOTP_handler: function () {
return Clipperz.Async.callbacks("MainController.createNewOTP_handler", [
MochiKit.Base.method(this.overlay(), 'show', "", true),
MochiKit.Base.method(this.user(), 'createNewOTP'),
MochiKit.Base.method(this, 'updateOTPListAndDetails'),
MochiKit.Base.method(this.overlay(), 'done', "", 0.5),
], {trace:false});
},
deleteOTPs_handler: function (aList) {
return Clipperz.Async.callbacks("MainController.deleteOTPs_handler", [
MochiKit.Base.method(this.overlay(), 'show', "", true),
MochiKit.Base.method(this.user(), 'deleteOTPs', aList),
MochiKit.Base.method(this, 'updateOTPListAndDetails'),
MochiKit.Base.method(this.overlay(), 'done', "", 0.5),
], {trace:false});
},
changeOTPLabel_handler: function (aReference, aLabel) {
return Clipperz.Async.callbacks("MainController.changeOTPLabel_handler", [
MochiKit.Base.method(this.overlay(), 'show', "", true),
MochiKit.Base.method(this.user(), 'changeOTPLabel', aReference, aLabel),
MochiKit.Base.method(this, 'updateOTPListAndDetails'),
MochiKit.Base.method(this.overlay(), 'done', "", 0.5),
], {trace:false});
},
//----------------------------------------------------------------------------
saveChanges: function () {
// TODO: handle errors while savings
return Clipperz.Async.callbacks("MainController.saveChanges", [

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,859 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
/**
* @preserve XDate v@VERSION
* Docs & Licensing: http://arshaw.com/xdate/
*/
/*
* Internal Architecture
* ---------------------
* An XDate wraps a native Date. The native Date is stored in the '0' property of the object.
* UTC-mode is determined by whether the internal native Date's toString method is set to
* Date.prototype.toUTCString (see getUTCMode).
*
*/
var XDate = (function(Date, Math, Array, undefined) {
/** @const */ var FULLYEAR = 0;
/** @const */ var MONTH = 1;
/** @const */ var DATE = 2;
/** @const */ var HOURS = 3;
/** @const */ var MINUTES = 4;
/** @const */ var SECONDS = 5;
/** @const */ var MILLISECONDS = 6;
/** @const */ var DAY = 7;
/** @const */ var YEAR = 8;
/** @const */ var WEEK = 9;
/** @const */ var DAY_MS = 86400000;
var ISO_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss(.fff)";
var ISO_FORMAT_STRING_TZ = ISO_FORMAT_STRING + "zzz";
var methodSubjects = [
'FullYear', // 0
'Month', // 1
'Date', // 2
'Hours', // 3
'Minutes', // 4
'Seconds', // 5
'Milliseconds', // 6
'Day', // 7
'Year' // 8
];
var subjectPlurals = [
'Years', // 0
'Months', // 1
'Days' // 2
];
var unitsWithin = [
12, // months in year
31, // days in month (sort of)
24, // hours in day
60, // minutes in hour
60, // seconds in minute
1000, // milliseconds in second
1 //
];
var formatStringRE = new RegExp(
"(([a-zA-Z])\\2*)|" + // 1, 2
"(\\(" + "(('.*?'|\\(.*?\\)|.)*?)" + "\\))|" + // 3, 4, 5 (allows for 1 level of inner quotes or parens)
"('(.*?)')" // 6, 7
);
var UTC = Date.UTC;
var toUTCString = Date.prototype.toUTCString;
var proto = XDate.prototype;
// This makes an XDate look pretty in Firebug and Web Inspector.
// It makes an XDate seem array-like, and displays [ <internal-date>.toString() ]
proto.length = 1;
proto.splice = Array.prototype.splice;
/* Constructor
---------------------------------------------------------------------------------*/
// TODO: in future, I'd change signature for the constructor regarding the `true` utc-mode param. ~ashaw
// I'd move the boolean to be the *first* argument. Still optional. Seems cleaner.
// I'd remove it from the `xdate`, `nativeDate`, and `milliseconds` constructors.
// (because you can simply call .setUTCMode(true) after)
// And I'd only leave it for the y/m/d/h/m/s/m and `dateString` constructors
// (because those are the only constructors that need it for DST-gap data-loss reasons)
// Should do this for 1.0
function XDate() {
return init(
(this instanceof XDate) ? this : new XDate(),
arguments
);
}
function init(xdate, args) {
var len = args.length;
var utcMode;
if (isBoolean(args[len-1])) {
utcMode = args[--len];
args = slice(args, 0, len);
}
if (!len) {
xdate[0] = new Date();
}
else if (len == 1) {
var arg = args[0];
if (arg instanceof Date || isNumber(arg)) {
xdate[0] = new Date(+arg);
}
else if (arg instanceof XDate) {
xdate[0] = _clone(arg);
}
else if (isString(arg)) {
xdate[0] = new Date(0);
xdate = parse(arg, utcMode || false, xdate);
}
}
else {
xdate[0] = new Date(UTC.apply(Date, args));
if (!utcMode) {
xdate[0] = coerceToLocal(xdate[0]);
}
}
if (isBoolean(utcMode)) {
setUTCMode(xdate, utcMode);
}
return xdate;
}
/* UTC Mode Methods
---------------------------------------------------------------------------------*/
proto.getUTCMode = methodize(getUTCMode);
function getUTCMode(xdate) {
return xdate[0].toString === toUTCString;
};
proto.setUTCMode = methodize(setUTCMode);
function setUTCMode(xdate, utcMode, doCoercion) {
if (utcMode) {
if (!getUTCMode(xdate)) {
if (doCoercion) {
xdate[0] = coerceToUTC(xdate[0]);
}
xdate[0].toString = toUTCString;
}
}else{
if (getUTCMode(xdate)) {
if (doCoercion) {
xdate[0] = coerceToLocal(xdate[0]);
}else{
xdate[0] = new Date(+xdate[0]);
}
// toString will have been cleared
}
}
return xdate; // for chaining
}
proto.getTimezoneOffset = function() {
if (getUTCMode(this)) {
return 0;
}else{
return this[0].getTimezoneOffset();
}
};
/* get / set / add / diff Methods (except for week-related)
---------------------------------------------------------------------------------*/
each(methodSubjects, function(subject, fieldIndex) {
proto['get' + subject] = function() {
return _getField(this[0], getUTCMode(this), fieldIndex);
};
if (fieldIndex != YEAR) { // because there is no getUTCYear
proto['getUTC' + subject] = function() {
return _getField(this[0], true, fieldIndex);
};
}
if (fieldIndex != DAY) { // because there is no setDay or setUTCDay
// and the add* and diff* methods use DATE instead
proto['set' + subject] = function(value) {
_set(this, fieldIndex, value, arguments, getUTCMode(this));
return this; // for chaining
};
if (fieldIndex != YEAR) { // because there is no setUTCYear
// and the add* and diff* methods use FULLYEAR instead
proto['setUTC' + subject] = function(value) {
_set(this, fieldIndex, value, arguments, true);
return this; // for chaining
};
proto['add' + (subjectPlurals[fieldIndex] || subject)] = function(delta, preventOverflow) {
_add(this, fieldIndex, delta, preventOverflow);
return this; // for chaining
};
proto['diff' + (subjectPlurals[fieldIndex] || subject)] = function(otherDate) {
return _diff(this, otherDate, fieldIndex);
};
}
}
});
function _set(xdate, fieldIndex, value, args, useUTC) {
var getField = curry(_getField, xdate[0], useUTC);
var setField = curry(_setField, xdate[0], useUTC);
var expectedMonth;
var preventOverflow = false;
if (args.length == 2 && isBoolean(args[1])) {
preventOverflow = args[1];
args = [ value ];
}
if (fieldIndex == MONTH) {
expectedMonth = (value % 12 + 12) % 12;
}else{
expectedMonth = getField(MONTH);
}
setField(fieldIndex, args);
if (preventOverflow && getField(MONTH) != expectedMonth) {
setField(MONTH, [ getField(MONTH) - 1 ]);
setField(DATE, [ getDaysInMonth(getField(FULLYEAR), getField(MONTH)) ]);
}
}
function _add(xdate, fieldIndex, delta, preventOverflow) {
delta = Number(delta);
var intDelta = Math.floor(delta);
xdate['set' + methodSubjects[fieldIndex]](
xdate['get' + methodSubjects[fieldIndex]]() + intDelta,
preventOverflow || false
);
if (intDelta != delta && fieldIndex < MILLISECONDS) {
_add(xdate, fieldIndex+1, (delta-intDelta)*unitsWithin[fieldIndex], preventOverflow);
}
}
function _diff(xdate1, xdate2, fieldIndex) { // fieldIndex=FULLYEAR is for years, fieldIndex=DATE is for days
xdate1 = xdate1.clone().setUTCMode(true, true);
xdate2 = XDate(xdate2).setUTCMode(true, true);
var v = 0;
if (fieldIndex == FULLYEAR || fieldIndex == MONTH) {
for (var i=MILLISECONDS, methodName; i>=fieldIndex; i--) {
v /= unitsWithin[i];
v += _getField(xdate2, false, i) - _getField(xdate1, false, i);
}
if (fieldIndex == MONTH) {
v += (xdate2.getFullYear() - xdate1.getFullYear()) * 12;
}
}
else if (fieldIndex == DATE) {
var clear1 = xdate1.toDate().setUTCHours(0, 0, 0, 0); // returns an ms value
var clear2 = xdate2.toDate().setUTCHours(0, 0, 0, 0); // returns an ms value
v = Math.round((clear2 - clear1) / DAY_MS) + ((xdate2 - clear2) - (xdate1 - clear1)) / DAY_MS;
}
else {
v = (xdate2 - xdate1) / [
3600000, // milliseconds in hour
60000, // milliseconds in minute
1000, // milliseconds in second
1 //
][fieldIndex - 3];
}
return v;
}
/* Week Methods
---------------------------------------------------------------------------------*/
proto.getWeek = function() {
return _getWeek(curry(_getField, this, false));
};
proto.getUTCWeek = function() {
return _getWeek(curry(_getField, this, true));
};
proto.setWeek = function(n, year) {
_setWeek(this, n, year, false);
return this; // for chaining
};
proto.setUTCWeek = function(n, year) {
_setWeek(this, n, year, true);
return this; // for chaining
};
proto.addWeeks = function(delta) {
return this.addDays(Number(delta) * 7);
};
proto.diffWeeks = function(otherDate) {
return _diff(this, otherDate, DATE) / 7;
};
function _getWeek(getField) {
return getWeek(getField(FULLYEAR), getField(MONTH), getField(DATE));
}
function getWeek(year, month, date) {
var d = new Date(UTC(year, month, date));
var week1 = getWeek1(
getWeekYear(year, month, date)
);
return Math.floor(Math.round((d - week1) / DAY_MS) / 7) + 1;
}
function getWeekYear(year, month, date) { // get the year that the date's week # belongs to
var d = new Date(UTC(year, month, date));
if (d < getWeek1(year)) {
return year - 1;
}
else if (d >= getWeek1(year + 1)) {
return year + 1;
}
return year;
}
function getWeek1(year) { // returns Date of first week of year, in UTC
var d = new Date(UTC(year, 0, 4));
d.setUTCDate(d.getUTCDate() - (d.getUTCDay() + 6) % 7); // make it Monday of the week
return d;
}
function _setWeek(xdate, n, year, useUTC) {
var getField = curry(_getField, xdate, useUTC);
var setField = curry(_setField, xdate, useUTC);
if (year === undefined) {
year = getWeekYear(
getField(FULLYEAR),
getField(MONTH),
getField(DATE)
);
}
var week1 = getWeek1(year);
if (!useUTC) {
week1 = coerceToLocal(week1);
}
xdate.setTime(+week1);
setField(DATE, [ getField(DATE) + (n-1) * 7 ]); // would have used xdate.addUTCWeeks :(
// n-1 because n is 1-based
}
/* Parsing
---------------------------------------------------------------------------------*/
XDate.parsers = [
parseISO
];
XDate.parse = function(str) {
return +XDate(''+str);
};
function parse(str, utcMode, xdate) {
var parsers = XDate.parsers;
var i = 0;
var res;
for (; i<parsers.length; i++) {
res = parsers[i](str, utcMode, xdate);
if (res) {
return res;
}
}
xdate[0] = new Date(str);
return xdate;
}
function parseISO(str, utcMode, xdate) {
var m = str.match(/^(\d{4})(-(\d{2})(-(\d{2})([T ](\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/);
if (m) {
var d = new Date(UTC(
m[1],
m[3] ? m[3] - 1 : 0,
m[5] || 1,
m[7] || 0,
m[8] || 0,
m[10] || 0,
m[12] ? Number('0.' + m[12]) * 1000 : 0
));
if (m[13]) { // has gmt offset or Z
if (m[14]) { // has gmt offset
d.setUTCMinutes(
d.getUTCMinutes() +
(m[15] == '-' ? 1 : -1) * (Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0))
);
}
}else{ // no specified timezone
if (!utcMode) {
d = coerceToLocal(d);
}
}
return xdate.setTime(+d);
}
}
/* Formatting
---------------------------------------------------------------------------------*/
proto.toString = function(formatString, settings, uniqueness) {
if (formatString === undefined || !valid(this)) {
return this[0].toString(); // already accounts for utc-mode (might be toUTCString)
}else{
return format(this, formatString, settings, uniqueness, getUTCMode(this));
}
};
proto.toUTCString = proto.toGMTString = function(formatString, settings, uniqueness) {
if (formatString === undefined || !valid(this)) {
return this[0].toUTCString();
}else{
return format(this, formatString, settings, uniqueness, true);
}
};
proto.toISOString = function() {
return this.toUTCString(ISO_FORMAT_STRING_TZ);
};
XDate.defaultLocale = '';
XDate.locales = {
'': {
monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
amDesignator: 'AM',
pmDesignator: 'PM'
}
};
XDate.formatters = {
i: ISO_FORMAT_STRING,
u: ISO_FORMAT_STRING_TZ
};
function format(xdate, formatString, settings, uniqueness, useUTC) {
var locales = XDate.locales;
var defaultLocaleSettings = locales[XDate.defaultLocale] || {};
var getField = curry(_getField, xdate, useUTC);
settings = (isString(settings) ? locales[settings] : settings) || {};
function getSetting(name) {
return settings[name] || defaultLocaleSettings[name];
}
function getFieldAndTrace(fieldIndex) {
if (uniqueness) {
var i = (fieldIndex == DAY ? DATE : fieldIndex) - 1;
for (; i>=0; i--) {
uniqueness.push(getField(i));
}
}
return getField(fieldIndex);
}
return _format(xdate, formatString, getFieldAndTrace, getSetting, useUTC);
}
function _format(xdate, formatString, getField, getSetting, useUTC) {
var m;
var subout;
var out = '';
while (m = formatString.match(formatStringRE)) {
out += formatString.substr(0, m.index);
if (m[1]) { // consecutive alphabetic characters
out += processTokenString(xdate, m[1], getField, getSetting, useUTC);
}
else if (m[3]) { // parenthesis
subout = _format(xdate, m[4], getField, getSetting, useUTC);
if (parseInt(subout.replace(/\D/g, ''), 10)) { // if any of the numbers are non-zero. or no numbers at all
out += subout;
}
}
else { // else if (m[6]) { // single quotes
out += m[7] || "'"; // if inner is blank, meaning 2 consecutive quotes = literal single quote
}
formatString = formatString.substr(m.index + m[0].length);
}
return out + formatString;
}
function processTokenString(xdate, tokenString, getField, getSetting, useUTC) {
var end = tokenString.length;
var replacement;
var out = '';
while (end > 0) {
replacement = getTokenReplacement(xdate, tokenString.substr(0, end), getField, getSetting, useUTC);
if (replacement !== undefined) {
out += replacement;
tokenString = tokenString.substr(end);
end = tokenString.length;
}else{
end--;
}
}
return out + tokenString;
}
function getTokenReplacement(xdate, token, getField, getSetting, useUTC) {
var formatter = XDate.formatters[token];
if (isString(formatter)) {
return _format(xdate, formatter, getField, getSetting, useUTC);
}
else if (isFunction(formatter)) {
return formatter(xdate, useUTC || false, getSetting);
}
switch (token) {
case 'fff' : return zeroPad(getField(MILLISECONDS), 3);
case 's' : return getField(SECONDS);
case 'ss' : return zeroPad(getField(SECONDS));
case 'm' : return getField(MINUTES);
case 'mm' : return zeroPad(getField(MINUTES));
case 'h' : return getField(HOURS) % 12 || 12;
case 'hh' : return zeroPad(getField(HOURS) % 12 || 12);
case 'H' : return getField(HOURS);
case 'HH' : return zeroPad(getField(HOURS));
case 'd' : return getField(DATE);
case 'dd' : return zeroPad(getField(DATE));
case 'ddd' : return getSetting('dayNamesShort')[getField(DAY)] || '';
case 'dddd' : return getSetting('dayNames')[getField(DAY)] || '';
case 'M' : return getField(MONTH) + 1;
case 'MM' : return zeroPad(getField(MONTH) + 1);
case 'MMM' : return getSetting('monthNamesShort')[getField(MONTH)] || '';
case 'MMMM' : return getSetting('monthNames')[getField(MONTH)] || '';
case 'yy' : return (getField(FULLYEAR)+'').substring(2);
case 'yyyy' : return getField(FULLYEAR);
case 't' : return _getDesignator(getField, getSetting).substr(0, 1).toLowerCase();
case 'tt' : return _getDesignator(getField, getSetting).toLowerCase();
case 'T' : return _getDesignator(getField, getSetting).substr(0, 1);
case 'TT' : return _getDesignator(getField, getSetting);
case 'z' :
case 'zz' :
case 'zzz' : return useUTC ? 'Z' : _getTZString(xdate, token);
case 'w' : return _getWeek(getField);
case 'ww' : return zeroPad(_getWeek(getField));
case 'S' :
var d = getField(DATE);
if (d > 10 && d < 20) return 'th';
return ['st', 'nd', 'rd'][d % 10 - 1] || 'th';
}
}
function _getTZString(xdate, token) {
var tzo = xdate.getTimezoneOffset();
var sign = tzo < 0 ? '+' : '-';
var hours = Math.floor(Math.abs(tzo) / 60);
var minutes = Math.abs(tzo) % 60;
var out = hours;
if (token == 'zz') {
out = zeroPad(hours);
}
else if (token == 'zzz') {
out = zeroPad(hours) + ':' + zeroPad(minutes);
}
return sign + out;
}
function _getDesignator(getField, getSetting) {
return getField(HOURS) < 12 ? getSetting('amDesignator') : getSetting('pmDesignator');
}
/* Misc Methods
---------------------------------------------------------------------------------*/
each(
[ // other getters
'getTime',
'valueOf',
'toDateString',
'toTimeString',
'toLocaleString',
'toLocaleDateString',
'toLocaleTimeString',
'toJSON'
],
function(methodName) {
proto[methodName] = function() {
return this[0][methodName]();
};
}
);
proto.setTime = function(t) {
this[0].setTime(t);
return this; // for chaining
};
proto.valid = methodize(valid);
function valid(xdate) {
return !isNaN(+xdate[0]);
}
proto.clone = function() {
return new XDate(this);
};
proto.clearTime = function() {
return this.setHours(0, 0, 0, 0); // will return an XDate for chaining
};
proto.toDate = function() {
return new Date(+this[0]);
};
/* Misc Class Methods
---------------------------------------------------------------------------------*/
XDate.now = function() {
return +new Date();
};
XDate.today = function() {
return new XDate().clearTime();
};
XDate.UTC = UTC;
XDate.getDaysInMonth = getDaysInMonth;
/* Internal Utilities
---------------------------------------------------------------------------------*/
function _clone(xdate) { // returns the internal Date object that should be used
var d = new Date(+xdate[0]);
if (getUTCMode(xdate)) {
d.toString = toUTCString;
}
return d;
}
function _getField(d, useUTC, fieldIndex) {
return d['get' + (useUTC ? 'UTC' : '') + methodSubjects[fieldIndex]]();
}
function _setField(d, useUTC, fieldIndex, args) {
d['set' + (useUTC ? 'UTC' : '') + methodSubjects[fieldIndex]].apply(d, args);
}
/* Date Math Utilities
---------------------------------------------------------------------------------*/
function coerceToUTC(date) {
return new Date(UTC(
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds()
));
}
function coerceToLocal(date) {
return new Date(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
date.getUTCMilliseconds()
);
}
function getDaysInMonth(year, month) {
return 32 - new Date(UTC(year, month, 32)).getUTCDate();
}
/* General Utilities
---------------------------------------------------------------------------------*/
function methodize(f) {
return function() {
return f.apply(undefined, [this].concat(slice(arguments)));
};
}
function curry(f) {
var firstArgs = slice(arguments, 1);
return function() {
return f.apply(undefined, firstArgs.concat(slice(arguments)));
};
}
function slice(a, start, end) {
return Array.prototype.slice.call(
a,
start || 0, // start and end cannot be undefined for IE
end===undefined ? a.length : end
);
}
function each(a, f) {
for (var i=0; i<a.length; i++) {
f(a[i], i);
};
}
function isString(arg) {
return typeof arg == 'string';
}
function isNumber(arg) {
return typeof arg == 'number';
}
function isBoolean(arg) {
return typeof arg == 'boolean';
}
function isFunction(arg) {
return typeof arg == 'function';
}
function zeroPad(n, len) {
len = len || 2;
n += '';
while (n.length < len) {
n = '0' + n;
}
return n;
}
// Export for Node.js
if (typeof module !== 'undefined' && module.exports) {
module.exports = XDate;
}
// AMD
if (typeof define === 'function' && define.amd) {
define([], function() {
return XDate;
});
}
return XDate;
})(Date, Math, Array);

View File

@ -26,7 +26,11 @@
"papaparse.repository": "https://github.com/mholt/PapaParse",
"papaparse.version": "4.1.1",
"papaparse.commit": "1c64d5c098570f243911e920bf7cbe170f69a9eb"
"papaparse.commit": "1c64d5c098570f243911e920bf7cbe170f69a9eb",
"xDate.repository": "https://github.com/arshaw/xdate",
"xDate.version": "0.8",
"xDate.commit": "f83cd8d63fab8cfe6f00ccba9041d2591daedb74"
},
"html.template": "index_template.html",
@ -68,6 +72,8 @@
"PapaParse/papaparse.js",
"-- PapaParse/papaparse.min.js",
"xDate/xdate.js",
"-- IT WOULD BE NICE TO BE ABLE TO GET RID OF THESE IMPORTS",
"Clipperz/YUI/Utils.js",
"Clipperz/YUI/DomHelper.js",
@ -158,7 +164,7 @@
"Clipperz/PM/UI/Components.js",
"Clipperz/PM/UI/Components/Overlay.js",
"Clipperz/PM/UI/Components/Button.js",
"Clipperz/PM/UI/Components/Checkbox.js",
"-- Clipperz/PM/UI/Components/Checkbox.js",
"Clipperz/PM/UI/Components/CardToolbar.js",
"Clipperz/PM/UI/Components/MessageBox.js",
"Clipperz/PM/UI/Components/DialogBox.js",
@ -179,18 +185,21 @@
"Clipperz/PM/UI/Components/ExtraFeatures/DevicePIN.js",
"Clipperz/PM/UI/Components/ExtraFeatures/Passphrase.js",
"Clipperz/PM/UI/Components/ExtraFeatures/OTP.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DeleteAccount.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataExport.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/Input.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CsvColumns.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CsvLabels.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CsvTitles.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CsvNotes.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CsvHidden.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/Import.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/Preview.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CSV/Columns.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CSV/Labels.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CSV/Titles.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CSV/Notes.js",
"Clipperz/PM/UI/Components/ExtraFeatures/DataImport/CSV/Hidden.js",
"Clipperz/PM/UI/Components/Cards/FavIcon.js",
"Clipperz/PM/UI/Components/Cards/List.js",
"Clipperz/PM/UI/Components/Cards/Detail.js",
@ -207,7 +216,6 @@
"-- Clipperz/PM/UI/MainDesktopController.js",
"Clipperz/PM/UI/DirectLoginController.js",
"Clipperz/PM/UI/ExportController.js",
"Clipperz/PM/UI/ImportController.js",
"Clipperz/PM/UI/ImportContext.js",
"main.js"
],

View File

@ -68,5 +68,3 @@ textarea {
//@import "sizes/wide";
//@import "sizes/extra-wide";
//@import "sizes/extra-short";

File diff suppressed because one or more lines are too long

View File

@ -34,7 +34,7 @@ html {
// @include user-select(none);
}
input {
input[type=text] {
-webkit-appearance: none;
}
@ -216,7 +216,7 @@ input {
& > div {
@include flex(auto);
@include overflow-auto;
@include overflow-auto();
// overflow: auto;
}
@ -233,6 +233,23 @@ input {
height: 100%;
// background-color: rgba( 0, 0, 0, 0.95);
background-color: black;
.extraFeature {
@include flexbox();
@include flex-direction(column);
height: 100%;
.header {
@include flex(none);
padding: 20px;
}
.content {
@include flex(auto);
// height: 100%;
padding: 0px 20px 20px 20px;
@include overflow-auto();
}
}
}
}
@ -444,7 +461,7 @@ div.cardContent {
@include flex(auto);
// overflow-y: scroll;
// overflow:auto;
@include overflow-auto;
@include overflow-auto();
}
}
}

View File

@ -89,7 +89,7 @@ refer to http://www.clipperz.com.
&.offlineCopy {
cursor: default;
}
}
}
&.open {
@ -170,17 +170,23 @@ refer to http://www.clipperz.com.
.extraFeatureContent {
border-right: 1px solid #222;
color: white;
// padding: 20px;
header {
display: none;
}
.extraFeature {
padding: 20px;
.header {
// padding-bottom: 20px;
h1 {
font-size: 20pt;
padding-bottom: 20px;
h1 {
font-size: 20pt;
}
p {
padding: 10px 0px;
}
}
form {
@ -209,15 +215,19 @@ refer to http://www.clipperz.com.
p {
@include flexbox;
@include flex-direction(row);
padding-top: 8px;
input {
width: 30px;
@include flex(auto);
}
span {
label {
@include flex(auto);
font-size: 12pt;
display: block;
cursor: pointer;
line-height: 1.5em;
}
}
@ -256,7 +266,7 @@ refer to http://www.clipperz.com.
color: white;
li {
padding-bottom: 40px;
padding-bottom: 30px;
}
}
@ -265,8 +275,9 @@ refer to http://www.clipperz.com.
}
.description {
max-width: 500px;
// max-width: 500px;
padding: 10px 0px 20px 0px;
// padding-bottom: 20px;
p {
font-size: 10pt;
@ -277,11 +288,16 @@ refer to http://www.clipperz.com.
em {
text-decoration: underline;
}
&.warning {
font-weight: bold;
color: white;
}
}
}
.button {
display: inline;
display: inline-block;
color: white;
background-color: $main-color;
@ -289,14 +305,462 @@ refer to http://www.clipperz.com.
font-size: 14pt;
border: 1px solid white;
padding: 6px 10px;
padding: 10px 14px;
&:after {
};
&.disabled {
background-color: #c0c0c0;
cursor: default;
}
}
}
.OTP {
height: 100%;
.header {
.description {
padding-bottom: 0px;
p {
margin-bottom: 0px;
padding-bottom: 0px;
}
}
}
.actions {
padding-left: 9px;
padding-top: 6px;
a {
@include icon-font();
cursor: pointer;
font-size: 18pt;
line-height: 1.1em;
&:hover {
color: green;
};
}
}
.selectMenu {
margin-top: 1em;
li {
display: inline-block;
margin-right: 1em;
padding:1em 0;
// a {
// text-decoration: underline;
// cursor: pointer;
// }
}
}
.otpList {
.otpDetail {
$detailPadding: 5px;
$detailMargin: 4px;
$labelPadding: 5px;
@include flexbox;
@include flex-direction(row);
border: 1px solid #222;
@include border-radius(5px);
padding: $detailPadding 0;
margin: $detailMargin 0;
.otpAction {
width: 40px;
text-align: center;
// @include align-self(center);
a {
@include icon-font();
cursor: pointer;
font-size: 16pt;
line-height: 1.1em;
&:hover {
color: red;
};
}
}
.otpInfo {
.otpPassword {
font-size: 14pt;
line-height: 1.3em;
@include user-select(text);
}
.otpLabel {
span {
font-size: 12pt;
color: gray;
padding: 4px 0px 4px 0px;
line-height: 1.3em;
cursor:pointer;
display: block;
}
input {
font-size: 12pt;
color: gray;
width: 100%;
border: 0px;
padding: 2px;
padding-left: 0px;
margin: 0px;
margin-bottom: 1px;
background-color: #333;
}
// .undefinedLabel {
// color: gray;
// }
}
.otpStatusInfo {
font-size: 8pt;
color: gray;
span {
padding-right: 10px;
}
}
}
&.REQUESTED, &.USED {
background-color: #222;
.otpPassword {
// color: gray;
}
}
}
}
.button {
margin:1em 1em 0 0;
}
table {
tr {
td {
a {
font-size: small;
padding: 0 1em;
text-decoration: underline;
cursor: pointer;
}
}
}
}
}
.dataImport {
.content {
display: block;
height: 100%;
@include flexbox;
@include flex-direction(column);
ul.stepNavbar {
@include flex(none);
padding-top: 5px;
padding-bottom: 5px;
text-align: center;
font-size: 24pt;
&.Input {
display: none;
}
li {
display: inline-block;
padding: 0px;
padding-right: 10px;
// margin-right:1em;
&.disabled {
color: gray;
}
&.active {
// text-decoration: underline;
}
}
}
.step {
@include flex(auto);
@include overflow-auto();
&.Input {
.description {
p {
width: 100%;
}
}
form {
.dropArea {
width: calc(100% - 6px);
text-align: center;
height: inherit;
line-height: 2em;
border: 3px dashed white;
background: black;
}
p {
margin: 10px 50%;
&.error {
color: red;
font-size: 10pt;
font-style: italic;
margin: 0px 0px 10px 0px;
}
}
textarea {
width:100%;
min-height:100px;
display: block;
margin: 1em 0;
border: 0;
}
}
}
&.Preview {
li.card {
@include flexbox;
@include flex-direction(row);
padding-top: 15px;
padding-bottom: 0px;
border-bottom: 1px solid #333;
&.archived {
background-color: #333;
}
input {
width: 30px;
margin-top: 6px;
@include flex(none);
}
div.cardContent {
@include flex(auto);
@include flexbox;
@include flex-direction(column);
height: auto;
h3 {
font-size: 24pt;
padding-bottom: 6px;
}
ul.tagList {
li {
display: inline-block;
padding-right: 10px;
padding-bottom: 5px;
&:before {
content: 'tag';
@include icon-font();
font-size: 10pt;
padding-right: 5px;
line-height: 28px;
color: #ccc;
}
}
}
dl {
dt {
font-size: 12pt;
color: gray;
line-height: 16pt;
}
dd {
font-size: 16pt;
color: white;
line-height: 24pt;
padding-bottom: 6pt;
&.PASSWORD {
font-family: clipperz-password;
// color: red;
}
}
}
p {
font-size: 10pt;
padding-top: 10px;
padding-bottom: 10px;
}
}
}
}
&.Import {
h5 {
padding-bottom: 15px;
}
}
table.csvTable {
width: 100%;
color: white;
border-collapse: collapse;
thead {
background-color: gray;
th {
align: left;
}
}
tbody {
td {
align: right;
}
}
td, th {
border: 1px solid #999;
padding: 0.5rem;
&.PASSWORD {
font-family: clipperz-password;
font-size: 14.9pt;
line-height: 10pt;
}
}
th {
background-color: #666;
&.title {
background-color: #888;
}
&.notes {
background-color: #aaa;
}
input {
color: white;
border: 0px;
padding: 0px;
font-size: 12pt;
font-weight: bold;
background-color: inherit;
}
}
}
}
.buttons {
@include flex(none);
text-align: center;
padding-top: 10px;
&.Input {
.button.back {
visibility: hidden;
}
}
&.Import {
.button.next {
visibility: hidden;
}
}
.button {
// border: 0px;
margin: 0px 5px;
span {
display: none;
}
&.back {
background-color: #c0c0c0;
&:before {
content: '<<';
}
}
&.next {
&.DISABLED {
background-color: #d3d3d3;
}
&:after {
content: '>>';
}
}
}
}
/*
.csvTable {
background: white;
margin: 1em 0;
}
.button {
margin-right:1em;
}
.jsonPreview {
width: 100%;
height:80%;
overflow: auto;
margin-top:1em;
h3 {
font-weight:bold;
}
ul {
margin-bottom:1em;
padding-left:1em;
li {
.label {
font-weight:bold;
}
}
}
}
*/
}
}
/*
.changePassphraseForm {
label {
@ -327,11 +791,15 @@ refer to http://www.clipperz.com.
}
*/
form {
input.valid + .invalidMsg, input.empty + .invalidMsg, input:focus + .invalidMsg, input.invalid:focus + .invalidMsg {
visibility: hidden;
}
}
}
}
.mainPage.narrow {
#extraFeaturesPanel {
.extraFeatureContent {

View File

@ -41,6 +41,7 @@ refer to http://www.clipperz.com.
<script type='text/javascript' src='../../../../../js/Clipperz/Crypto/Base.js'></script>
<script type='text/javascript' src='../../../../../js/Clipperz/Crypto/BigInt.js'></script>
<script type='text/javascript' src='../../../../../js/Clipperz/Crypto/AES.js'></script>
<script type='text/javascript' src='../../../../../js/Clipperz/Crypto/AES_2.js'></script>
<script type='text/javascript' src='../../../../../js/Clipperz/Crypto/SHA.js'></script>
<script type='text/javascript' src='../../../../../js/Clipperz/Crypto/PRNG.js'></script>
<script type='text/javascript' src='../../../../../js/Clipperz/Crypto/SRP.js'></script>

View File

@ -95,8 +95,8 @@ var tests = {
newPassphrase = 'tset';
proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:true, isDefault:true, readOnly:false});
user = new Clipperz.PM.DataModel.User({username:'test', getPassphraseFunction:function () { return 'test';}});
user2 = new Clipperz.PM.DataModel.User({username:'test', getPassphraseFunction:function () { return otp;}});
user = new Clipperz.PM.DataModel.User({username:'test', getPassphraseFunction: MochiKit.Base.partial(MochiKit.Async.succeed, 'test')});
user2 = new Clipperz.PM.DataModel.User({username:'test', getPassphraseFunction: MochiKit.Base.partial(MochiKit.Async.succeed, otp)});
deferredResult = new Clipperz.Async.Deferred("changePassphraseAndLoginUsingOtp_test", someTestArgs);
deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['test_test_with_otps']);
@ -107,7 +107,7 @@ var tests = {
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(1, "This account has only a single card");
deferredResult.addMethod(user, 'changePassphrase', newPassphrase);
deferredResult.addMethod(user, 'changePassphrase', MochiKit.Base.partial(MochiKit.Async.succeed, newPassphrase));
deferredResult.addMethod(user, 'logout');
deferredResult.addMethod(user2, 'login');
@ -181,7 +181,145 @@ var tests = {
return deferredResult;
},
//-------------------------------------------------------------------------
'loginWithANewOTP_test': function(someTestArgs) {
var deferredResult;
var proxy;
var user;
var user2;
var user3;
var username;
var passphrase;
username = "1";
passphrase = "1";
proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:true, isDefault:true, readOnly:false});
user = new Clipperz.PM.DataModel.User({username:username, getPassphraseFunction:MochiKit.Base.partial(MochiKit.Async.succeed, passphrase)});
user2 = new Clipperz.PM.DataModel.User({username:username, getPassphraseFunction:function () { return "WILL_BE_CHANGED_WITH_OTP";}});
user3 = new Clipperz.PM.DataModel.User({username:username, getPassphraseFunction:function () { return "WILL_BE_CHANGED_WITH_OTP";}});
deferredResult = new Clipperz.Async.Deferred("loginUserWithANewOTP_test", someTestArgs);
deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['1/1_data']);
deferredResult.addMethod(user, 'login');
deferredResult.addMethod(user, 'getRecords');
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(1, "This account has one single card");
deferredResult.addMethod(user, 'getOneTimePasswords');
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(0, "There should be no OTPs initially");
deferredResult.addMethod(user, 'createNewOTP');
deferredResult.addMethod(user, 'getOneTimePasswords');
deferredResult.setValue('otpList');
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(1, "There should be one single OTP now");
deferredResult.getValue('otpList');
deferredResult.addCallback(function(aList) {return MochiKit.Base.partial(MochiKit.Async.succeed, aList[0].password()); });
deferredResult.addMethod(user2, 'setPassphraseFunction'),
deferredResult.addMethod(user, 'logout');
deferredResult.addMethod(user2, 'login');
deferredResult.addMethod(user2, 'getRecords');
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(1, "This account has one single card");
deferredResult.getValue('otpList');
deferredResult.addCallback(function(aList) {return MochiKit.Base.partial(MochiKit.Async.succeed, aList[0].password()); });
deferredResult.addMethod(user3, 'setPassphraseFunction'),
deferredResult.addMethod(user2, 'logout');
deferredResult.addMethod(user3, 'login');
deferredResult.shouldFail("Second login with the same OTP should fail");
deferredResult.callback();
return deferredResult;
},
'deleteOTP_test': function(someTestArgs) {
var deferredResult;
var proxy;
var user;
var user2;
var username;
var passphrase;
username = "1";
passphrase = "1";
proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:true, isDefault:true, readOnly:false});
user = new Clipperz.PM.DataModel.User({username:username, getPassphraseFunction:MochiKit.Base.partial(MochiKit.Async.succeed, passphrase)});
user2 = new Clipperz.PM.DataModel.User({username:username, getPassphraseFunction:function () { return "WILL_BE_CHANGED_WITH_OTP";}});
deferredResult = new Clipperz.Async.Deferred("deleteOTP_test", someTestArgs);
deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['1/1_data']);
deferredResult.addMethod(user, 'login');
deferredResult.addMethod(user, 'getOneTimePasswords');
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(0, "There should be no OTPs initially");
deferredResult.addMethod(user, 'createNewOTP');
deferredResult.addMethod(user, 'createNewOTP');
deferredResult.addMethod(user, 'createNewOTP');
deferredResult.addMethod(user, 'createNewOTP');
deferredResult.addMethod(user, 'createNewOTP');
deferredResult.addMethod(user, 'createNewOTP');
deferredResult.addMethod(user, 'getOneTimePasswords');
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(6, "There should be 6 OTPs now");
deferredResult.addMethod(user, 'getOneTimePasswords');
deferredResult.addCallback(function(aList) {return [aList[0].reference(), aList[1].reference()];});
deferredResult.addMethod(user, 'deleteOTPs');
deferredResult.addMethod(user, 'getOneTimePasswords');
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(4, "There should be 4 OTPs now");
deferredResult.addMethod(user, 'getOneTimePasswords');
deferredResult.addCallback(function(aList) {return [aList[0].reference(), aList[1].reference(), aList[2].reference()];});
deferredResult.addMethod(user, 'deleteOTPs');
deferredResult.addMethod(user, 'getOneTimePasswords');
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(1, "There should be 1 OTP now");
deferredResult.addMethod(user, 'getOneTimePasswords');
deferredResult.addCallback(function(aList) {return MochiKit.Base.partial(MochiKit.Async.succeed, aList[0].password()); });
deferredResult.addMethod(user2, 'setPassphraseFunction');
deferredResult.addMethod(user, 'logout');
deferredResult.addMethod(user2, 'login');
deferredResult.addMethod(user2, 'getRecords');
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(1, "Login with the remaining OTP should work");
deferredResult.addMethod(user2, 'getOneTimePasswords');
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(1, "There should be 1 remaining OTP now");
deferredResult.collectResults({
'oneTimePasswords': MochiKit.Base.method(user2, 'getOneTimePasswords'),
'oneTimePasswordsDetails': MochiKit.Base.method(user2, 'getOneTimePasswordsDetails')
});
deferredResult.addCallback(function(someData) {
return someData['oneTimePasswordsDetails'][someData['oneTimePasswords'][0].reference()]['status'];
});
deferredResult.addTest('USED', "The remaining OTP should have 'USED' status");
deferredResult.addMethod(user2, 'getOneTimePasswords');
deferredResult.addCallback(function(aList) {return [aList[0].reference()];});
deferredResult.addMethod(user2, 'deleteOTPs');
deferredResult.addMethod(user2, 'getOneTimePasswords');
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(0, "There should be no OTPs left after the deletion of the last one");
deferredResult.callback();
return deferredResult;
},
//-------------------------------------------------------------------------
'syntaxFix': MochiKit.Base.noop
};
@ -190,4 +328,4 @@ var tests = {
//#############################################################################
SimpleTest.runDeferredTests("Clipperz.PM.DataModel.OneTimePassword", tests, {trace:false});
SimpleTest.runDeferredTests("Clipperz.PM.DataModel.OneTimePassword", tests, {trace:true});

View File

@ -23,7 +23,7 @@ refer to http://www.clipperz.com.
"use strict";
Clipperz.PM.DataModel.Record.tagChar = '#'; // Simplify tests using a 'regular' char instead of an UTF-8 reserved one
// Clipperz.PM.DataModel.Record.tagChar = '#'; // Simplify tests using a 'regular' char instead of an UTF-8 reserved one
Clipperz.Crypto.PRNG.defaultRandomGenerator().fastEntropyAccumulationForTestingPurpose();
var tests = {
@ -80,6 +80,33 @@ var tests = {
return deferredResult;
},
//-------------------------------------------------------------------------
'createNewRecord_test': function(someTestArgs) {
var deferredResult;
var proxy;
var user;
proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:true, isDefault:true, readOnly:false});
user = new Clipperz.PM.DataModel.User({username:"1", getPassphraseFunction:MochiKit.Base.partial(MochiKit.Async.succeed, "1")});
deferredResult = new Clipperz.Async.Deferred("deleteOTP_test", someTestArgs);
deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['1/1_data']);
deferredResult.addMethod(user, 'login');
deferredResult.addMethod(user, 'createNewRecord');
deferredResult.setValue('newRecord');
deferredResult.addMethodcaller('setLabel', "A record");
deferredResult.getValue('newRecord');
deferredResult.addMethodcaller('addField', {'label':"Field 1", 'value':"Value 1", 'isHidden':false});
deferredResult.getValue('newRecord');
deferredResult.addMethodcaller('label');
deferredResult.addTest("A record", "Record returns the right value for label");
deferredResult.callback();
return deferredResult;
},
//-------------------------------------------------------------------------
'createRecordWithoutAllRequiredParameters_test': function (someTestArgs) {
@ -1434,6 +1461,8 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
return filterRecordsWithRegExp(aUser, Clipperz.PM.DataModel.Record.regExpForSearch(aSearchTerm));
}
var tagChar = Clipperz.PM.DataModel.Record.tagChar;
proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false});
user = new Clipperz.PM.DataModel.User({username:'tag', getPassphraseFunction:function () { return 'tag';}});
@ -1452,11 +1481,11 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
deferredResult.addMethod(user, 'getRecordsInfo', Clipperz.PM.DataModel.Record.defaultCardInfo);
deferredResult.addCallback(MochiKit.Base.map, Clipperz.Base.itemgetter('_searchableContent'));
deferredResult.addCallback(SimpleTest.eq, [
'Card 1 #Tag1 #Tag2 ',
'Card 2 #Tag1 #Tag3 ',
'Card 3 #Tag1 ',
'Card 4 #Tag3 ',
'Card 5 #Tag4 ',
'Card 1 '+tagChar+'Tag1 '+tagChar+'Tag2 ',
'Card 2 '+tagChar+'Tag1 '+tagChar+'Tag3 ',
'Card 3 '+tagChar+'Tag1 ',
'Card 4 '+tagChar+'Tag3 ',
'Card 5 '+tagChar+'Tag4 ',
'Card 6 '
]);
@ -1920,7 +1949,7 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
var deferredResult;
var proxy;
var user;
var recordID = 'eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01'
var recordID = '327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83'
var originalFieldReference = 'bfd7624054e1eb6f1849082714f4016e300bce66645c7a7370276d82767cf125';
proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false});
@ -2002,7 +2031,7 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
var deferredResult;
var proxy;
var user;
var recordID = 'eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01'
var recordID = '327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83'
var originalFieldReference = 'bfd7624054e1eb6f1849082714f4016e300bce66645c7a7370276d82767cf125';
proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false});
@ -2066,7 +2095,7 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
var deferredResult;
var proxy;
var user;
var recordID = 'eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01'
var recordID = '327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83'
// var originalFieldReference = 'bfd7624054e1eb6f1849082714f4016e300bce66645c7a7370276d82767cf125';
proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false});
@ -2132,7 +2161,7 @@ deferredResult.addCallback(function (aValue) { console.log("FIELDS", aValue); re
var deferredResult;
var proxy;
var user;
var recordID = 'eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01'
var recordID = '327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83'
// var originalFieldReference = 'bfd7624054e1eb6f1849082714f4016e300bce66645c7a7370276d82767cf125';
proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:false, isDefault:true, readOnly:false});

View File

@ -1832,14 +1832,14 @@ testData = {
},
'onetimePasswords': {
// OTP: 'yaxx k7ww - f8y6 tqz5 - 58b6 th44 - 9cwv q0fg',
'7074103e8ce35f813dbfb9c90665bd66ba3f5b1c9e4fa7a3d8aee679b7a38102': { // reference
'c3664af5744319c6d3b874895f803df19cb0492acf27cb51912110d023ba9b38': { // key
'reference': "c3664af5744319c6d3b874895f803df19cb0492acf27cb51912110d023ba9b38",
'user': "9a984e219b07f9b645ef35f4de938b4741abe2e0b4adc88b40e9367170c91cc8",
'status': 'ACTIVE', // 1: 'ACTIVE', 2: 'REQUESTED', 3: 'USED', 4: 'DISABLED'
'creation_date': "2010-02-09 17:57:14",
'request_date': "4001-01-01 09:00:00",
'usage_date': "4001-01-01 09:00:00",
// 'key': "7074103e8ce35f813dbfb9c90665bd66ba3f5b1c9e4fa7a3d8aee679b7a38102",
'key': "7074103e8ce35f813dbfb9c90665bd66ba3f5b1c9e4fa7a3d8aee679b7a38102",
"key_checksum": "53739910c97d74c80c6028eb3293ffbc652def811d9aa11725fefa3139dfcf29",
"data": "aN3rPl5rusBWXveUpjKqZNFLRPWJgH2Zs8HYQIaS65QObQFWFTZ8lRpBXFoPlvSOHcHQpEavZUuq31Y/2Y9sI/scvmZjQ8UEaT2GY9NiWJVswGq1W3AX8zs32jOgf1L5iBVxK54mfig2vXFoL8lG0JGGY1zHZXlkCvFPWuvwuCcH+uRE0oP3it0FvNFBV4+TiiGnGYgD9LPAVevzr/Doq5UXjn9VplVU+upeDTWY+7rlOdIOnZG/A9P9/dQtsyMb5c5zABD2FNpQQ40JDfG/nHt5WvfuWmPwUisW1181oBAd5BwF9LgVHdrhcSh8OuUL7rdbKTPTlWT826I6JNrFMzYGMY+NV6gllDvc6eCNrgI98ABhL1AoZNpAXXuCy4uQhEYmj+O71C/eXEDw+crMAXiCn6SZrbTM8GT5TQ5yF2NcxhudopO4qoILjnwEHZZ+i37kRDFg6oCBccCD67oHTPexUkSqnKIIYLli5CdmE7UdvX6LuVG/VYJKevOUgMf0UzHDPgvtlp3gsSo09TfNPOtoeAiogL6cAHb1seZwv+6E8Pz7WqkkOTsBQYeHIfPE0OnQPDtUjVRA5MTTX5zt6rCCNDKNbqfkPu8V4am26ykaWOSTXZYIcfnywkG0TfPzdAyQvyxdUyl/r1b36bclQFiXcRzkz9zS9xx14Il3QjYXRbIFWcwm/mEFltBFPdATKo5Zh+wcTLiFh56YEUVa9/h6oN8281X6zxH4DOw=",
'version': "0.3"
@ -1900,127 +1900,229 @@ testData = {
},
// username: 'tag', passphrase: 'tag'
"38d2354c878a06fbdcccc493a23fc6d9be06eebb4f66952bbc1b736824b123f9": {
"s": "e332fc34a678a6be7f025403c8007c1dff962cbae8acd1a490018ffb7477536d",
"v": "afa4836f22b8a1fbfb0b6a563c2356c0438ef9a9dd56877af8e8421d0706e300",
"version": "0.2",
"maxNumberOfRecords": "100",
"userDetails": '{"records":{"index":{"021c1512cd4eb8e05fd82d4b0d86cb6edcc7be0c06e32bc21ed74beafefdb33d":"0","ae9bf2d068fe27ee0922371ec519c8a75b30e0bf88d72a8606a74f97a69badb8":"1","bdf315d1c65c1de200a384cfc23fb0b6e140905830c9d42f25585165fb8851fb":"2","5358946680c0ea40e36bbb9f2f62a0f5f2aabb66efe643ee26d3715c2c17feee":"3","501e451d5e3f4d5d69c5e9430fcee63800bae551ff4ebd89c46d3d5c654c083f":"4","b869b4b928e26b8c669d7e39da1df55406336b259edf19b032ee2e475347e8fa":"5"},"data":"EPB52oY8PC/GYaSORI9ytiiPIoDrIQrgu02OpI168mnT0BRtonqfvMUxejJ/nvgWCNgx7lCYMmGzaDCQtvmAj7q8DQHjgcG1HJS/BjaEJdlK+mIa0WmVED1nGi7HzZDP8XgeR6FSAS0MsIXqBI1BtOhPizWaQxQ7q/wiZpGwSMu4R8I/16u6bWNSdsIR52GkI2RXrXxit0BSs/ZHefCAEQVprygBJYTAsuBlI9UdOrtOG3ODCMGzzY69kC10YBPxEHgVdsJ8xGlM4QxrDoYcAXdqmHVNc8X3fsOSN6CdC+TPOYQ7kp7RsiWlAy/a26ASmfDioXikiEqSZ/m7Hz5EYBfdPIKzTf1HaTaj1EXPFwQ16Kkdyjlrc3OKl79Pe2JZ5lAfmf2aqHXxRFGk4aedDvwhMENdZYgea8IwBmmnalF+5U67gSY0YYe2ZixTnFKg1MRHAaGZSCJhZlbmcTKRzI4crNrRs03F9V10EfaUyIQdktRBNsKexQaJr1dxq524XJDSdIzu3A++8ks/khLFzrAQySWcjv2PmaFq/wf19HzVLOwJAkYv7Kp8AGpXpdNrtm2wNTiQ84a6KOuV8YW3jz8/m0VrsVe8wImHrih58lWi7SOHZ1y/ZjtSXwa8OEo/vdR3+SOFJujgYaoDcxLknMxXvjYjSUViLmAcHkM3SRx4ucGPlL9EOTFHhJJI3pynhubXYME/ujlkLRZEPQoSRKUWHAQ5X+O+1SwgPVPcSKqeEhM6kWhnXXpBWmUE+sSXfC4CC4Jqw+Mfrx6Mdx9ayep8ssge4ZIaDIVDUu3p2aEAQehwOvGzYec9jZfvhdODO0MK9cCV6D3iv8I="},"directLogins":{"index":{},"data":"VXRt7prFeG9+iLmWhkJVGKi9"},"preferences":{"data":"xPvWlbPo8iwq4OiL6BhiHWCg"},"oneTimePasswords":{"data":"UFam/MJzsuh7hiMUXruQrI94"},"version":"0.1"}',
"statistics": "Pf9V+hgwcEUMUbiYj7lhOvy/",
"userDetailsVersion": "0.4",
"records": {
"021c1512cd4eb8e05fd82d4b0d86cb6edcc7be0c06e32bc21ed74beafefdb33d": {
"data": "coBU7HDciqWdtPrGoRE8x307+S0fXZPjdxCypClm9qXdQIJCTEohg8E3OHWydsZMI852VNgoIfhjv5+E8qzxCAwb+eh/2Lsv4+VWTi7MxVAgsqHk+NGOlpTzlBOtq/V86lZHuZajy4UWsLhoJvQxMNlV4Gryk+9G+MQv/ITYi/d6mRNMNQ==",
"version": "0.4",
"creationDate": "Thu Jul 03 13:44:30 GMT 2014",
"updateDate": "Thu Jul 03 13:44:30 GMT 2014",
"accessDate": "Thu Jul 03 13:44:30 GMT 2014",
"currentVersion": "d29b343b00ef60309365c2a7ff2dee750271c86cbe9671c19de184c5b7cfd71a",
"versions": {
"d29b343b00ef60309365c2a7ff2dee750271c86cbe9671c19de184c5b7cfd71a": {
"header": "####",
"data": "NXmNUj/dXJImYLfqLmCFJeJh7GI51jzCitJBZS6bDoD1YzNj98weM4fW7AweTpvloojR1/1bzrvuEk13YKRYeDqD3WLKDC1QpAhmBj7EPkYfmOFizPRTpb3i0CkH2HQ2+OqCi27BdrMbU2VTNgBAtGl2RLQYa9yLJpz6wD1Bone192X3BuxWp2/9h8gYVqrmfT/aN5UGZlGHtJscjoqQME0=",
"version": "0.4",
"creationDate": "Thu Jul 03 13:44:30 GMT 2014",
"updateDate": "Thu Jul 03 13:44:30 GMT 2014",
"accessDate": "Thu Jul 03 13:44:30 GMT 2014"
}
}
},
"ae9bf2d068fe27ee0922371ec519c8a75b30e0bf88d72a8606a74f97a69badb8": {
"data": "+vz60IxhoBYTxnCKoiEVmr9BVBKQ2vFtDyJqvpA468m6+ngCQZUAuyRvuTy7HGtHh/8Fc+VHZy6sjy6z5noVIhSG8uQnOpMj7lIxr471DC2suKgYzM3buiHehmzF8W+eNIOumyysAgQxvIXF/12zYVGiYUKBw9s9NgayeCXWZCktGyKp/w==",
"version": "0.4",
"creationDate": "Thu Jul 03 13:44:54 GMT 2014",
"updateDate": "Thu Jul 03 13:44:54 GMT 2014",
"accessDate": "Thu Jul 03 13:44:54 GMT 2014",
"currentVersion": "23ed84136b59b351c10a1f608c84af426651755180ff3b0154eeedeb9f9d9efc",
"versions": {
"23ed84136b59b351c10a1f608c84af426651755180ff3b0154eeedeb9f9d9efc": {
"header": "####",
"data": "tw4PEJ2+bpv69m2JlKlUo2TXTSWzhd5qVuuEXa78Fzz2TAA+SLIAn/0YMLMZqJpT9/UX2A5llYIg4tasSTiScdKduvLqTwjZ/wQL9zkWw9vlb5DwUboLHYTD24rLQDKtEuZtQzkds7cKmTS8JocHiePtOnw4WYav7d1mNrEFFz6RJU4RklG5T0ATN59UTg9uRseN4zSSIl21Np3kb7/QSEI=",
"version": "0.4",
"creationDate": "Thu Jul 03 13:44:54 GMT 2014",
"updateDate": "Thu Jul 03 13:44:54 GMT 2014",
"accessDate": "Thu Jul 03 13:44:54 GMT 2014"
}
}
},
"bdf315d1c65c1de200a384cfc23fb0b6e140905830c9d42f25585165fb8851fb": {
"data": "TGS6lm8w6OVFEnyQkjzJnS/FWx1h0LgrdpJwRKMz+/o5pJi0S9KGSzihtc6iek2/Dqixqh/Nxi8fZY2tNsB6n50aNHR+gVX4LHhgQOpw/3aYcWMd9WIuwdMPKDY6RiCBDLwxCDuhtKDCsGkd105+v58h08wYeCc8WDEg2vsR1bXQQZMYQw==",
"version": "0.4",
"creationDate": "Thu Jul 03 13:45:16 GMT 2014",
"updateDate": "Thu Jul 03 13:45:16 GMT 2014",
"accessDate": "Thu Jul 03 13:45:16 GMT 2014",
"currentVersion": "a7a90ac83249e6aeba78ebde4ba798a8c912975d55c5315fec320f42ed67175d",
"versions": {
"a7a90ac83249e6aeba78ebde4ba798a8c912975d55c5315fec320f42ed67175d": {
"header": "####",
"data": "iNlrPeH/d9jtI91isY3r8n1QUtUYXVT1mhrRisyGwrpQo+4XCqMYattH31MrFh1hZCmplL0+vq8+bWo1JqA1HclSpAetyDIQ7NnlqGlnZYMBII3GgBfknHJRIlnm+RNFFQeOHWjhyL7YQVpmmC0ONJ++HaAmPxvOWzP1g/rKbsEoQyIBqqG32H3nxSU/bUVo+F5K5sfSNWnayPsDlEv5pNM=",
"version": "0.4",
"creationDate": "Thu Jul 03 13:45:16 GMT 2014",
"updateDate": "Thu Jul 03 13:45:16 GMT 2014",
"accessDate": "Thu Jul 03 13:45:16 GMT 2014"
}
}
},
"5358946680c0ea40e36bbb9f2f62a0f5f2aabb66efe643ee26d3715c2c17feee": {
"data": "vEEFjeDfMPr1ahbjPA56Su31uJvyXcOAfJ2SfCdd0xTld6tj7iRsh81g1UYw8W1nvyRg4ymSiXIwEmx5cyOa/lj7eWRM0AqFy3qfqSUJze8Xn8u9RYiE0rt4LZLSDk9FogvvMIXKDbCvUbmyPSA83bmjCvDkiCvhqY4MhwGE8Jk/liE3YQ==",
"version": "0.4",
"creationDate": "Thu Jul 03 13:45:38 GMT 2014",
"updateDate": "Thu Jul 03 13:45:38 GMT 2014",
"accessDate": "Thu Jul 03 13:45:38 GMT 2014",
"currentVersion": "211175a91451fa30b3c989ebe46ec2e7593b88e74bbe9ccb0578315750e31626",
"versions": {
"211175a91451fa30b3c989ebe46ec2e7593b88e74bbe9ccb0578315750e31626": {
"header": "####",
"data": "Ouu/BSyDHstyLcddZd1EsSQRWKTjQUt9kFNSYoxQOUBXWw+ukqDPPMXYZWggjrGAbT5hFf4yoEqi2VCCAdZT5juwmMrSEGZjoFSSw/e5OYH3ptoZAQ4ThNo75R2oJfI7/kUMKBeeXE4zykRYWv4aEOGHtSKpnzydGHnvDFfpxInFx2MH1eIYH+BpCujMDN0aDNFLRWl9isZ070DioTNgvI8=",
"version": "0.4",
"creationDate": "Thu Jul 03 13:45:38 GMT 2014",
"updateDate": "Thu Jul 03 13:45:38 GMT 2014",
"accessDate": "Thu Jul 03 13:45:38 GMT 2014"
}
}
},
"501e451d5e3f4d5d69c5e9430fcee63800bae551ff4ebd89c46d3d5c654c083f": {
"data": "7aTkEoBButGoPiXCpAfn+CrJpzfw5zau1kEBY2uWBJ3cAT3XbkDgAodPmU/HDTV1pb7+2a2sAnwThDWCSkJfMGG6i3eRlQJj9WZfIEIv0W7UDMGbZdhuweSEPTf7GMR4BtHPMgVgLToSn+YOc0tasavVGz3+rV9qNFf9Rf2PrGKFQkY7Kg==",
"version": "0.4",
"creationDate": "Thu Jul 03 13:46:07 GMT 2014",
"updateDate": "Thu Jul 03 13:46:07 GMT 2014",
"accessDate": "Thu Jul 03 13:46:07 GMT 2014",
"currentVersion": "a03817cced057a4bc693db014eb356277d9b91df6a08c55f45c97b5d4b08003b",
"versions": {
"a03817cced057a4bc693db014eb356277d9b91df6a08c55f45c97b5d4b08003b": {
"header": "####",
"data": "blB9bmxoEh//V40FoD9tLbQGyHiFAcLn9Rj4KDOp5DQRiLLlxqgvShShXEVtjUmiTjnHGlkNm6RQtZTaJrG84nV29QncBxLKMNnZKmqW2fXp2uyd4k+zzg4r7ilC29Vh1WP6bNxapwivDUw1n1Y9bEsX8LSAtCSIseIXszciSnLQ6ktyzBGzuDppc/cQ94TfGFKTSwZdts3d34Kxh8q0NTE=",
"version": "0.4",
"creationDate": "Thu Jul 03 13:46:07 GMT 2014",
"updateDate": "Thu Jul 03 13:46:07 GMT 2014",
"accessDate": "Thu Jul 03 13:46:07 GMT 2014"
}
}
},
"b869b4b928e26b8c669d7e39da1df55406336b259edf19b032ee2e475347e8fa": {
"data": "cZQZboAoipwpOdCuvyXfS1T0ul0FnoWBAB0jqwQ282mWHjOBM/JI/7jk4z20qaYp/3XTGpVvbYPqvQn/+KyYiLT8aR/JkiFQ38wioaaq6X0Qg38Y2itPXMZjTnSGnf0boPf6mP8s9k8GGubuk4Gi1n4uJECLESX4In3VGq02hHcoxfZwMQ==",
"version": "0.4",
"creationDate": "Thu Jul 03 13:46:28 GMT 2014",
"updateDate": "Thu Jul 03 13:46:28 GMT 2014",
"accessDate": "Thu Jul 03 13:46:28 GMT 2014",
"currentVersion": "b29e478ce7352c85234a4040514255a0162f62ab60880d5d959d86c365f0f088",
"versions": {
"b29e478ce7352c85234a4040514255a0162f62ab60880d5d959d86c365f0f088": {
"header": "####",
"data": "zOv15MnYpRcPyUoig13mGdWktiSLStCla0RVOp3laAEBBxplEao1RXWd/FOjKRmf2rx1Ma+s1s+CB1r2Z8t8VFRLDLQQk+m4LuGzY/QvHXbvcWMtNFxb8Ax464lX2w8FHZDBbcN29nfroeMX0Cq9oD6BKVrRdouUESHpX/oMDZ6VJRfZhv7ZIqdEET3+6u8Ub+OHdYyktYH4OHJTpW5eRc8=",
"version": "0.4",
"creationDate": "Thu Jul 03 13:46:28 GMT 2014",
"updateDate": "Thu Jul 03 13:46:28 GMT 2014",
"accessDate": "Thu Jul 03 13:46:28 GMT 2014"
}
}
}
}
"userDetailsVersion": "0.4",
"accountInfo": {
"features": [
"UPDATE_CREDENTIALS",
"EDIT_CARD",
"CARD_DETAILS",
"ADD_CARD",
"DELETE_CARD",
"OFFLINE_COPY",
"LIST_CARDS"
],
"paymentVerificationPending": false,
"currentSubscriptionType": "EARLY_ADOPTER",
"isExpiring": false,
"latestActiveLevel": "EARLY_ADOPTER",
"payments": [],
"featureSet": "FULL",
"latestActiveThreshold": "-1.00000000",
"referenceDate": "Wed, 17 June 2015 12:44:29 UTC",
"isExpired": false,
"expirationDate": "Mon, 01 January 4001 00:00:00 UTC"
},
"s": "e332fc34a678a6be7f025403c8007c1dff962cbae8acd1a490018ffb7477536d",
"records": {
"501e451d5e3f4d5d69c5e9430fcee63800bae551ff4ebd89c46d3d5c654c083f": {
"reference": "501e451d5e3f4d5d69c5e9430fcee63800bae551ff4ebd89c46d3d5c654c083f",
"updateDate": "Thu, 03 July 2014 13:46:07 UTC",
"data": "7aTkEoBButGoPiXCpAfn+CrJpzfw5zau1kEBY2uWBJ3cAT3XbkDgAodPmU/HDTV1pb7+2a2sAnwThDWCSkJfMGG6i3eRlQJj9WZfIEIv0W7UDMGbZdhuweSEPTf7GMR4BtHPMgVgLToSn+YOc0tasavVGz3+rV9qNFf9Rf2PrGKFQkY7Kg==",
"accessDate": "Wed, 17 June 2015 12:44:07 UTC",
"versions": {"a03817cced057a4bc693db014eb356277d9b91df6a08c55f45c97b5d4b08003b": {
"reference": "a03817cced057a4bc693db014eb356277d9b91df6a08c55f45c97b5d4b08003b",
"updateDate": "Thu, 03 July 2014 13:46:07 UTC",
"data": "blB9bmxoEh//V40FoD9tLbQGyHiFAcLn9Rj4KDOp5DQRiLLlxqgvShShXEVtjUmiTjnHGlkNm6RQtZTaJrG84nV29QncBxLKMNnZKmqW2fXp2uyd4k+zzg4r7ilC29Vh1WP6bNxapwivDUw1n1Y9bEsX8LSAtCSIseIXszciSnLQ6ktyzBGzuDppc/cQ94TfGFKTSwZdts3d34Kxh8q0NTE=",
"accessDate": "Wed, 17 June 2015 12:44:07 UTC",
"header": "####",
"creationDate": "Thu, 03 July 2014 13:46:07 UTC",
"version": "0.4"
}},
"creationDate": "Thu, 03 July 2014 13:46:07 UTC",
"version": "0.4",
"currentVersion": "a03817cced057a4bc693db014eb356277d9b91df6a08c55f45c97b5d4b08003b",
"oldestUsedEncryptedVersion": "0.4"
},
"5358946680c0ea40e36bbb9f2f62a0f5f2aabb66efe643ee26d3715c2c17feee": {
"reference": "5358946680c0ea40e36bbb9f2f62a0f5f2aabb66efe643ee26d3715c2c17feee",
"updateDate": "Thu, 03 July 2014 13:45:38 UTC",
"data": "vEEFjeDfMPr1ahbjPA56Su31uJvyXcOAfJ2SfCdd0xTld6tj7iRsh81g1UYw8W1nvyRg4ymSiXIwEmx5cyOa/lj7eWRM0AqFy3qfqSUJze8Xn8u9RYiE0rt4LZLSDk9FogvvMIXKDbCvUbmyPSA83bmjCvDkiCvhqY4MhwGE8Jk/liE3YQ==",
"accessDate": "Wed, 17 June 2015 12:43:49 UTC",
"versions": {"211175a91451fa30b3c989ebe46ec2e7593b88e74bbe9ccb0578315750e31626": {
"reference": "211175a91451fa30b3c989ebe46ec2e7593b88e74bbe9ccb0578315750e31626",
"updateDate": "Thu, 03 July 2014 13:45:38 UTC",
"data": "Ouu/BSyDHstyLcddZd1EsSQRWKTjQUt9kFNSYoxQOUBXWw+ukqDPPMXYZWggjrGAbT5hFf4yoEqi2VCCAdZT5juwmMrSEGZjoFSSw/e5OYH3ptoZAQ4ThNo75R2oJfI7/kUMKBeeXE4zykRYWv4aEOGHtSKpnzydGHnvDFfpxInFx2MH1eIYH+BpCujMDN0aDNFLRWl9isZ070DioTNgvI8=",
"accessDate": "Wed, 17 June 2015 12:43:49 UTC",
"header": "####",
"creationDate": "Thu, 03 July 2014 13:45:38 UTC",
"version": "0.4"
}},
"creationDate": "Thu, 03 July 2014 13:45:38 UTC",
"version": "0.4",
"currentVersion": "211175a91451fa30b3c989ebe46ec2e7593b88e74bbe9ccb0578315750e31626",
"oldestUsedEncryptedVersion": "0.4"
},
"b869b4b928e26b8c669d7e39da1df55406336b259edf19b032ee2e475347e8fa": {
"reference": "b869b4b928e26b8c669d7e39da1df55406336b259edf19b032ee2e475347e8fa",
"updateDate": "Thu, 03 July 2014 13:46:28 UTC",
"data": "cZQZboAoipwpOdCuvyXfS1T0ul0FnoWBAB0jqwQ282mWHjOBM/JI/7jk4z20qaYp/3XTGpVvbYPqvQn/+KyYiLT8aR/JkiFQ38wioaaq6X0Qg38Y2itPXMZjTnSGnf0boPf6mP8s9k8GGubuk4Gi1n4uJECLESX4In3VGq02hHcoxfZwMQ==",
"accessDate": "Fri, 10 April 2015 09:27:54 UTC",
"versions": {"b29e478ce7352c85234a4040514255a0162f62ab60880d5d959d86c365f0f088": {
"reference": "b29e478ce7352c85234a4040514255a0162f62ab60880d5d959d86c365f0f088",
"updateDate": "Thu, 03 July 2014 13:46:28 UTC",
"data": "zOv15MnYpRcPyUoig13mGdWktiSLStCla0RVOp3laAEBBxplEao1RXWd/FOjKRmf2rx1Ma+s1s+CB1r2Z8t8VFRLDLQQk+m4LuGzY/QvHXbvcWMtNFxb8Ax464lX2w8FHZDBbcN29nfroeMX0Cq9oD6BKVrRdouUESHpX/oMDZ6VJRfZhv7ZIqdEET3+6u8Ub+OHdYyktYH4OHJTpW5eRc8=",
"accessDate": "Fri, 10 April 2015 09:27:54 UTC",
"header": "####",
"creationDate": "Thu, 03 July 2014 13:46:28 UTC",
"version": "0.4"
}},
"creationDate": "Thu, 03 July 2014 13:46:28 UTC",
"version": "0.4",
"currentVersion": "b29e478ce7352c85234a4040514255a0162f62ab60880d5d959d86c365f0f088",
"oldestUsedEncryptedVersion": "0.4"
},
"ae9bf2d068fe27ee0922371ec519c8a75b30e0bf88d72a8606a74f97a69badb8": {
"reference": "ae9bf2d068fe27ee0922371ec519c8a75b30e0bf88d72a8606a74f97a69badb8",
"updateDate": "Thu, 03 July 2014 13:44:54 UTC",
"data": "+vz60IxhoBYTxnCKoiEVmr9BVBKQ2vFtDyJqvpA468m6+ngCQZUAuyRvuTy7HGtHh/8Fc+VHZy6sjy6z5noVIhSG8uQnOpMj7lIxr471DC2suKgYzM3buiHehmzF8W+eNIOumyysAgQxvIXF/12zYVGiYUKBw9s9NgayeCXWZCktGyKp/w==",
"accessDate": "Wed, 17 June 2015 12:43:13 UTC",
"versions": {"23ed84136b59b351c10a1f608c84af426651755180ff3b0154eeedeb9f9d9efc": {
"reference": "23ed84136b59b351c10a1f608c84af426651755180ff3b0154eeedeb9f9d9efc",
"updateDate": "Thu, 03 July 2014 13:44:54 UTC",
"data": "tw4PEJ2+bpv69m2JlKlUo2TXTSWzhd5qVuuEXa78Fzz2TAA+SLIAn/0YMLMZqJpT9/UX2A5llYIg4tasSTiScdKduvLqTwjZ/wQL9zkWw9vlb5DwUboLHYTD24rLQDKtEuZtQzkds7cKmTS8JocHiePtOnw4WYav7d1mNrEFFz6RJU4RklG5T0ATN59UTg9uRseN4zSSIl21Np3kb7/QSEI=",
"accessDate": "Wed, 17 June 2015 12:43:13 UTC",
"header": "####",
"creationDate": "Thu, 03 July 2014 13:44:54 UTC",
"version": "0.4"
}},
"creationDate": "Thu, 03 July 2014 13:44:54 UTC",
"version": "0.4",
"currentVersion": "23ed84136b59b351c10a1f608c84af426651755180ff3b0154eeedeb9f9d9efc",
"oldestUsedEncryptedVersion": "0.4"
},
"bdf315d1c65c1de200a384cfc23fb0b6e140905830c9d42f25585165fb8851fb": {
"reference": "bdf315d1c65c1de200a384cfc23fb0b6e140905830c9d42f25585165fb8851fb",
"updateDate": "Thu, 03 July 2014 13:45:16 UTC",
"data": "TGS6lm8w6OVFEnyQkjzJnS/FWx1h0LgrdpJwRKMz+/o5pJi0S9KGSzihtc6iek2/Dqixqh/Nxi8fZY2tNsB6n50aNHR+gVX4LHhgQOpw/3aYcWMd9WIuwdMPKDY6RiCBDLwxCDuhtKDCsGkd105+v58h08wYeCc8WDEg2vsR1bXQQZMYQw==",
"accessDate": "Wed, 17 June 2015 12:43:36 UTC",
"versions": {"a7a90ac83249e6aeba78ebde4ba798a8c912975d55c5315fec320f42ed67175d": {
"reference": "a7a90ac83249e6aeba78ebde4ba798a8c912975d55c5315fec320f42ed67175d",
"updateDate": "Thu, 03 July 2014 13:45:16 UTC",
"data": "iNlrPeH/d9jtI91isY3r8n1QUtUYXVT1mhrRisyGwrpQo+4XCqMYattH31MrFh1hZCmplL0+vq8+bWo1JqA1HclSpAetyDIQ7NnlqGlnZYMBII3GgBfknHJRIlnm+RNFFQeOHWjhyL7YQVpmmC0ONJ++HaAmPxvOWzP1g/rKbsEoQyIBqqG32H3nxSU/bUVo+F5K5sfSNWnayPsDlEv5pNM=",
"accessDate": "Wed, 17 June 2015 12:43:36 UTC",
"header": "####",
"creationDate": "Thu, 03 July 2014 13:45:16 UTC",
"version": "0.4"
}},
"creationDate": "Thu, 03 July 2014 13:45:16 UTC",
"version": "0.4",
"currentVersion": "a7a90ac83249e6aeba78ebde4ba798a8c912975d55c5315fec320f42ed67175d",
"oldestUsedEncryptedVersion": "0.4"
},
"021c1512cd4eb8e05fd82d4b0d86cb6edcc7be0c06e32bc21ed74beafefdb33d": {
"reference": "021c1512cd4eb8e05fd82d4b0d86cb6edcc7be0c06e32bc21ed74beafefdb33d",
"updateDate": "Thu, 03 July 2014 13:44:30 UTC",
"data": "coBU7HDciqWdtPrGoRE8x307+S0fXZPjdxCypClm9qXdQIJCTEohg8E3OHWydsZMI852VNgoIfhjv5+E8qzxCAwb+eh/2Lsv4+VWTi7MxVAgsqHk+NGOlpTzlBOtq/V86lZHuZajy4UWsLhoJvQxMNlV4Gryk+9G+MQv/ITYi/d6mRNMNQ==",
"accessDate": "Wed, 17 June 2015 12:42:51 UTC",
"versions": {"d29b343b00ef60309365c2a7ff2dee750271c86cbe9671c19de184c5b7cfd71a": {
"reference": "d29b343b00ef60309365c2a7ff2dee750271c86cbe9671c19de184c5b7cfd71a",
"updateDate": "Thu, 03 July 2014 13:44:30 UTC",
"data": "NXmNUj/dXJImYLfqLmCFJeJh7GI51jzCitJBZS6bDoD1YzNj98weM4fW7AweTpvloojR1/1bzrvuEk13YKRYeDqD3WLKDC1QpAhmBj7EPkYfmOFizPRTpb3i0CkH2HQ2+OqCi27BdrMbU2VTNgBAtGl2RLQYa9yLJpz6wD1Bone192X3BuxWp2/9h8gYVqrmfT/aN5UGZlGHtJscjoqQME0=",
"accessDate": "Wed, 17 June 2015 12:42:51 UTC",
"header": "####",
"creationDate": "Thu, 03 July 2014 13:44:30 UTC",
"version": "0.4"
}},
"creationDate": "Thu, 03 July 2014 13:44:30 UTC",
"version": "0.4",
"currentVersion": "d29b343b00ef60309365c2a7ff2dee750271c86cbe9671c19de184c5b7cfd71a",
"oldestUsedEncryptedVersion": "0.4"
}
},
"v": "afa4836f22b8a1fbfb0b6a563c2356c0438ef9a9dd56877af8e8421d0706e300",
"version": "0.2",
"userDetails": "{\"records\":{\"index\":{\"021c1512cd4eb8e05fd82d4b0d86cb6edcc7be0c06e32bc21ed74beafefdb33d\":\"0\",\"ae9bf2d068fe27ee0922371ec519c8a75b30e0bf88d72a8606a74f97a69badb8\":\"1\",\"bdf315d1c65c1de200a384cfc23fb0b6e140905830c9d42f25585165fb8851fb\":\"2\",\"5358946680c0ea40e36bbb9f2f62a0f5f2aabb66efe643ee26d3715c2c17feee\":\"3\",\"501e451d5e3f4d5d69c5e9430fcee63800bae551ff4ebd89c46d3d5c654c083f\":\"4\",\"b869b4b928e26b8c669d7e39da1df55406336b259edf19b032ee2e475347e8fa\":\"5\"},\"data\":\"5oWOZGW\/lnLtQ\/JzAiZCP6gDN8A3Vpnona1uCdNl5j2n8Q5JHOrxG2hYbIpO1pQ0H3PrKsYrMqstuHggaTB0\/Rzm\/ficHaUWhRJqmQrrqu3S14UQ6Ts7ZE+me+JHC67dEMBxxSRvw9DMErR2ph1O1AUWlEqjqDt99UU6T\/mA\/0aqfJu9fsFC5AVk\/QH6Pzp8xMK1oX4AhB9H3hVZzWvIr2jYFTyVRldSfD5h1HKNt2cCyEWvAzSvBmIsTcTJIyah7Uv8ZVRk1zFTvdD2eWgNkKrQo8fMdCalw1cT7lQ3esccKllp1ydQZSNZ6365HYu1fC4negehCzMz5eu3Y\/Q236F2rZo63cXHQgDi38GWM+cdXL7JgXdPMfMudyCKIxJnFVFK5VzghMU\/n7i\/nYZD1K04Q\/E6fKP4kKkJlNd3JEpr\/nM9cVpWXB5ghrI4pvo3kxNy9DWVL5mD8eANmqZyNZqii2jTpPF+oZS8NtKWPSwLs8HbcnS\/eRVgv0pE8mLCp0nPxqfBHrlORX5phzSURONZZIvqxh\/s0uxYEQFuzFl2G+NhtbhL25Q4rLLUZrFAnlp1qgoH1sUl0B4WzEN8qw56uOtNesRv63NoETIhvOYNU89t4CsCnRznuWm774KWNnbuBXTiDw\/rreQU59PgXgr7BPuJFvzonqiDTiqRHVWDYnTP5YSWBwtEEr24vfCjWx1BmoxytH0hRhWcJOqJGW3rhLHkBTLg9rDUFBKR5HJbjzW1Htw\/Yhq9PzhRVbkTNdvVzVv6cMiFNeuJG2LyBeSe2ioDcyWSYmHedEYnn3PRXEJhD3AAdpANj2MGP5yvZ7dHNXGXz2DgkhRO24PoiHuehe2jMx9Zkw==\"},\"directLogins\":{\"index\":{},\"data\":\"Ud\/RyTy7CT291MeUshmhjoT6\"},\"preferences\":{\"data\":\"hseSN5pYGCAs144bQVlVHtop\"},\"oneTimePasswords\":{\"data\":\"2GS0OVjaU42kt4yinfQUXK6c\"},\"version\":\"0.1\"}",
"maxNumberOfRecords": 100,
"statistics": "Pf9V+hgwcEUMUbiYj7lhOvy/"
}
}
};}(),
//-------------------------------------------------------------------------
'1/1_data': function () { return {
users:{
'catchAllUser': {
__masterkey_test_value__: 'masterkey',
s: '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00',
v: '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00'
},
// username: '1', passphrase: '1'
"3073058ba04e7d35a161af27f07ddb24d603d2a6418b4140010b7cdb24ff4774": {
"userDetailsVersion": "0.4",
"accountInfo": {
"features": [
"UPDATE_CREDENTIALS",
"EDIT_CARD",
"CARD_DETAILS",
"ADD_CARD",
"DELETE_CARD",
"OFFLINE_COPY",
"LIST_CARDS"
],
"paymentVerificationPending": false,
"currentSubscriptionType": "EARLY_ADOPTER",
"isExpiring": false,
"latestActiveLevel": "EARLY_ADOPTER",
"payments": [],
"featureSet": "FULL",
"latestActiveThreshold": "-1.00000000",
"referenceDate": "Wed, 17 June 2015 13:43:24 UTC",
"isExpired": false,
"expirationDate": "Mon, 01 January 4001 00:00:00 UTC"
},
"s": "a363479cf2f229466e01f32bfb783800745c740b4a955a83c0fbf561028bdba7",
"records": {
"327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83": {
"reference": "327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83",
"updateDate": "Wed, 17 June 2015 13:42:54 UTC",
"data": "SPU205djKKDJ7mg6txo5B9z0myET/8lcwjuT+IEMJVIw4JdBq+gXQ9cxESKQjvuuGnZyNRPzOKgfXlJTtgUwtPSwmpigrnUMilxcBtcpOkVe+kZXe+N1J7iMli1xVHfpmrhSEA8E+zZ4OEnGuHiJgX9qzNix15rbeZLX7bXBsWUowHuIvw==",
"accessDate": "Wed, 17 June 2015 13:42:54 UTC",
"versions": {
"eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01": {
"reference": "eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01",
"updateDate": "Wed, 17 June 2015 13:42:54 UTC",
"data": "Em0rLpHXv9IKtyVLWPGXeM6erk52v/1nYyh0DqT/UnL2pZjU4PRAPvKYZa5yhkx7QqgfWJNtdMUKbGN4DAfmKYcaAZZl0fcYRddAAduWUdG4Zuwk41hJ5gzomf0oqAtQU8lPK4VQFo1iyOctWVJ19h+/fZbUvLVzWOWXBMvItu054w+jHMDWF/hFuL09HQoyMz1JeXJroRTVx47pLbVVkUYNBOCz5FSTIRUqdmeSkaSiLtrLI0349SVrvPEXWPghGgKdWBiJCn/lFivmRKenJtGpxJwiV1CfQIS00JeyEFg+zqWBg31A8cp70NSHXVuprYbruWaMRdwZlI4PNl5JuzGdhL/z3iR+3TevcOIrHkvgH4/zVV+BHYlIPMpwj1zs4C0XNr+DF7F3FX3gCuU4CbMdzt6GoPYJnKAAfM+bZGQpAZp2sdzuZWdGi2EuHNicjLhdZF/TcbeGNjVG6RdliFvrqC+o0wet3OO7MRyHDU1AqWNPTgAo7gIp33DH0lf2HOtXJH8HjOXNSa7fgW5JhyfUxyddEONJwXUOL9KbkXsSElJjk+W0/QiWqw==",
"accessDate": "Wed, 17 June 2015 13:42:54 UTC",
"header": "####",
"creationDate": "Wed, 17 June 2015 13:42:54 UTC",
"version": "0.4"
}
},
"creationDate": "Wed, 17 June 2015 13:42:54 UTC",
"version": "0.4",
"currentVersion": "eb9a01d0094fcd8f3cbf4f875b7f4c43afa2bb796b5787badf75fba1b3e77c01",
"oldestUsedEncryptedVersion": "0.4"
}
},
"v": "baef74012d85813dca18ab0218bb27c63ffabc414223f05c015b4b73485b8fed",
"version": "0.2",
"userDetails": "{\"records\":{\"index\":{\"327139a4d4cfbdb61c06b4cfa009f9cb05ef2f3e3703b6b071bcdb4213b2ca83\":\"0\"},\"data\":\"HF+LSoX668EqFNltHvK7vaiO4srKLI5AzxXmlTssJNUCgSoWcvyE3psARFbnRamrUKHsFXipQypQjLH3W9eHNCt1im4GnxLGJskFJx0IjfYwrIJP1MNThUoz2EMMGAJiZS4WAFxnpMj3pD1JpFvNVtC+5B1NNQ==\"},\"directLogins\":{\"index\":{},\"data\":\"AJvILR1GywXePCwZd6HE27im\"},\"preferences\":{\"data\":\"5sK4mjoBQ3ONd4oKpxB9yWcV\"},\"oneTimePasswords\":{\"data\":\"J1yST9vO+PGreat6f8W7McqF\"},\"version\":\"0.1\"}",
"maxNumberOfRecords": 100,
"statistics": "h4SscRLQf8X+q5+Qhfs6MG44"
}
}
};}(),
//-------------------------------------------------------------------------
/*
'tt/tt_data': function () { return {

View File

@ -2022,8 +2022,8 @@ console.log("PROXY", proxy);
newPassphrase = 'zreppilc';
proxy = new Clipperz.PM.Proxy.Test({shouldPayTolls:true, isDefault:true, readOnly:false});
user = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction:function () { return 'clipperz';}});
user2 = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction:function () { return newPassphrase;}});
user = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction: MochiKit.Base.partial(MochiKit.Async.succeed, 'clipperz')});
user2 = new Clipperz.PM.DataModel.User({username:'joe', getPassphraseFunction: MochiKit.Base.partial(MochiKit.Async.succeed, newPassphrase)});
deferredResult = new Clipperz.Async.Deferred("changePassphrase_test", someTestArgs);
deferredResult.addMethod(proxy.dataStore(), 'setupWithEncryptedData', testData['joe_clipperz_offline_copy_data']);
@ -2034,7 +2034,7 @@ console.log("PROXY", proxy);
deferredResult.addCallback(MochiKit.Base.itemgetter('length'));
deferredResult.addTest(20, "This account has oly a single card");
deferredResult.addMethod(user, 'changePassphrase', newPassphrase);
deferredResult.addMethod(user, 'changePassphrase', MochiKit.Base.partial(MochiKit.Async.succeed, newPassphrase));
deferredResult.addMethod(user, 'logout');
deferredResult.addMethod(user2, 'login');