Merge pull request #999 from esroyo/gpg-key-multiple-addresses

Gpg key multiple addresses
This commit is contained in:
RainLoop Team 2016-04-19 20:26:36 +03:00
commit dd824179f1
12 changed files with 198 additions and 77 deletions

View file

@ -426,25 +426,36 @@ class AppUser extends AbstractApp
if (oItem && oItem.primaryKey)
{
var
aEmails = [],
aUsers = [],
oPrimaryUser = oItem.getPrimaryUser(),
sUser = (oPrimaryUser && oPrimaryUser.user) ? oPrimaryUser.user.userId.userid
: (oItem.users && oItem.users[0] ? oItem.users[0].userId.userid : '')
;
oEmail.clear();
oEmail.mailsoParse(sUser);
if (oItem.users) {
_.each(oItem.users, (oItem) => {
oEmail.clear();
oEmail.mailsoParse(oItem.userId.userid);
if (oEmail.validate())
{
aEmails.push(oEmail.email);
aUsers.push(oItem.userId.userid);
}
});
}
if (oEmail.validate())
if (aEmails.length)
{
aKeys.push(new OpenPgpKeyModel(
iIndex,
oItem.primaryKey.getFingerprint(),
oItem.primaryKey.getKeyId().toHex().toLowerCase(),
sUser,
oEmail.email,
aUsers,
aEmails,
oItem.isPrivate(),
oItem.armor())
oItem.armor(),
sUser)
);
}
}

View file

@ -201,7 +201,19 @@
*/
MessageModel.prototype.getRecipientsEmails = function ()
{
return _.compact(_.uniq(_.map(this.to.concat(this.cc), function (oItem) {
return this.getEmails(['to', 'cc']);
};
/**
* @param {Array} aProperties
* @return {Array}
*/
MessageModel.prototype.getEmails = function (aProperties)
{
var self = this;
return _.compact(_.uniq(_.map(_.reduce(aProperties, function (aCarry, sProperty) {
return aCarry.concat(self[sProperty]);
}, []), function (oItem) {
return oItem ? oItem.email : '';
})));
};
@ -1033,4 +1045,4 @@
module.exports = MessageModel;
}());
}());

View file

@ -18,23 +18,25 @@
* @param {string} iIndex
* @param {string} sGuID
* @param {string} sID
* @param {string} sUserID
* @param {string} sEmail
* @param {array} aUserIDs
* @param {array} aEmails
* @param {boolean} bIsPrivate
* @param {string} sArmor
* @param {string} sUserID
* @constructor
*/
function OpenPgpKeyModel(iIndex, sGuID, sID, sUserID, sEmail, bIsPrivate, sArmor)
function OpenPgpKeyModel(iIndex, sGuID, sID, aUserIDs, aEmails, bIsPrivate, sArmor, sUserID)
{
AbstractModel.call(this, 'OpenPgpKeyModel');
this.index = iIndex;
this.id = sID;
this.guid = sGuID;
this.user = sUserID;
this.email = sEmail;
this.users = aUserIDs;
this.emails = aEmails;
this.armor = sArmor;
this.isPrivate = !!bIsPrivate;
this.selectUser(sUserID);
this.deleteAccess = ko.observable(false);
}
@ -45,7 +47,9 @@
OpenPgpKeyModel.prototype.id = '';
OpenPgpKeyModel.prototype.guid = '';
OpenPgpKeyModel.prototype.user = '';
OpenPgpKeyModel.prototype.users = [];
OpenPgpKeyModel.prototype.email = '';
OpenPgpKeyModel.prototype.emails = [];
OpenPgpKeyModel.prototype.armor = '';
OpenPgpKeyModel.prototype.isPrivate = false;
@ -74,6 +78,26 @@
return oKey && oKey.keys ? oKey.keys : null;
};
OpenPgpKeyModel.prototype.select = function (sPattern, sProperty)
{
var index = this[sProperty].indexOf(sPattern);
if (index !== -1) {
this.user = this.users[index];
this.email = this.emails[index];
}
}
OpenPgpKeyModel.prototype.selectUser = function (sUser)
{
this.select(sUser, 'users');
}
OpenPgpKeyModel.prototype.selectEmail = function (sEmail)
{
this.select(sEmail, 'emails');
}
module.exports = OpenPgpKeyModel;
}());
}());

View file

@ -59,7 +59,7 @@
PgpUserStore.prototype.findPublicKeysByEmail = function (sEmail)
{
return _.compact(_.flatten(_.map(this.openpgpkeysPublic(), function (oItem) {
var oKey = oItem && sEmail === oItem.email ? oItem : null;
var oKey = oItem && -1 !== oItem.emails.indexOf(sEmail) ? oItem : null;
return oKey ? oKey.getNativeKeys() : [null];
}), true));
};
@ -99,7 +99,7 @@
PgpUserStore.prototype.findPublicKeyByEmailNotNative = function (sEmail)
{
return _.find(this.openpgpkeysPublic(), function (oItem) {
return oItem && sEmail === oItem.email;
return oItem && -1 !== oItem.emails.indexOf(sEmail);
}) || null;
};
@ -110,7 +110,7 @@
PgpUserStore.prototype.findPrivateKeyByEmailNotNative = function (sEmail)
{
return _.find(this.openpgpkeysPrivate(), function (oItem) {
return oItem && sEmail === oItem.email;
return oItem && -1 !== oItem.emails.indexOf(sEmail);
}) || null;
};
@ -125,7 +125,7 @@
oPrivateKeys = [],
oPrivateKey = null,
oKey = _.find(this.openpgpkeysPrivate(), function (oItem) {
return oItem && sEmail === oItem.email;
return oItem && -1 !== oItem.emails.indexOf(sEmail);
})
;
@ -292,7 +292,7 @@
bEncrypted = mDom.hasClass('encrypted'),
bSigned = mDom.hasClass('signed'),
oVerControl = null,
aRecipients = oRainLoopMessage ? oRainLoopMessage.getRecipientsEmails() : [],
aRecipients = oRainLoopMessage ? oRainLoopMessage.getEmails(['from', 'to', 'cc']) : [],
sData = ''
;

View file

@ -33,8 +33,14 @@
min-height: 40px;
&-wrp {
overflow: hidden;
text-overflow: ellipsis;
&:hover {
overflow: auto;
}
&:hover .key-list__item-name {
overflow: visible;
}
&.empty {
text-align: center;
@ -48,33 +54,39 @@
color: #333;
white-space: nowrap;
padding-bottom: 4px;
&-delete {
display: inline;
cursor: pointer;
margin-right: 5px;
&:last-child {
padding-bottom: 0;
}
&-name {
margin-right: 5px;
&-delete {
cursor: pointer;
&.disabled {
cursor: not-allowed;
}
}
&-names {
color: #333;
display: inline;
&.empty {
color: red;
}
}
&-name {
overflow: hidden;
text-overflow: ellipsis;
}
&-error {
margin-right: 5px;
color: red;
display: inline;
}
&-hash {
margin-right: 5px;
color: #aaa;
display: inline;
}
}
}
@ -82,6 +94,10 @@
.key-actions {
margin-top: 10px;
min-height: 40px;
select option.even {
background-color: #f5f5f5;
}
}
}
@ -103,12 +119,18 @@
white-space: nowrap;
&__radio {
padding-right: 5px;
padding: 3px 5px 0 0;
vertical-align: top;
}
&__name {
border-bottom: 1px solid transparent;
}
&:hover {
&__names {
display: inline-block;
&:hover .key-list__item__name {
border-bottom: 1px dashed #555;
}
}
@ -129,4 +151,4 @@
}
}
}
}
}

View file

@ -19,6 +19,7 @@
.open-pgp-key-img {
margin-right: 10px;
vertical-align: top;
.svg-icon {
width: 12px;
@ -33,6 +34,11 @@
line-height: 22px;
cursor: default;
}
.open-pgp-key-user-address:first-child {
line-height: 30px;
margin-bottom: -4px;
}
}
.open-pgp-key-item {
@ -61,4 +67,4 @@
}
}
}
}
}

View file

@ -7,6 +7,11 @@
line-height: 17px;
font-size: 16px;
vertical-align: text-top;
&.disabled,
.disabled & {
color: grey;
}
}
.icon-folder, .icon-folder-add, .icon-list {

View file

@ -31,6 +31,14 @@
var self = this;
this.optionsCaption = Translator.i18n('PGP_NOTIFICATIONS/ADD_A_PUBLICK_KEY');
this.addOptionClass = function (oDomOption, oItem)
{
self.defautOptionsAfterRender(oDomOption, oItem);
if (oItem) {
oDomOption.classList.add(oItem['class']);
}
};
this.notification = ko.observable('');
@ -54,13 +62,17 @@
}, this);
this.publicKeysOptions = ko.computed(function () {
return _.compact(_.map(PgpStore.openpgpkeysPublic(), function (oKey) {
return -1 < Utils.inArray(oKey, self.encryptKeysView()) ? null : {
'id': oKey.guid,
'name': '(' + oKey.id.substr(-8).toUpperCase() + ') ' + oKey.user,
'key': oKey
};
}));
return _.compact(_.flatten(_.map(PgpStore.openpgpkeysPublic(), function (oKey, iIndex) {
return -1 < Utils.inArray(oKey, self.encryptKeysView()) ? null :
_.map(oKey.users, function (sUser) {
return {
'id': oKey.guid,
'name': '(' + oKey.id.substr(-8).toUpperCase() + ') ' + sUser,
'key': oKey,
'class': iIndex % 2 ? 'odd' : 'even'
};
});
}), true));
});
this.submitRequest = ko.observable(false);
@ -236,7 +248,8 @@
aKeys.push({
'empty': !oOption.key,
'selected': ko.observable(!!oOption.key),
'user': oOption.key.user,
'removable': this.signKey().id !== oOption.key.id,
'users': oOption.key.users,
'hash': oOption.key.id.substr(-8).toUpperCase(),
'key': oOption.key
});
@ -360,11 +373,12 @@
if (oIdentity && oIdentity.email())
{
sEmail = oIdentity.email();
aRec.unshift(sEmail);
oKey = PgpStore.findPrivateKeyByEmailNotNative(sEmail);
if (oKey)
{
this.signKey({
'user': oKey.user || sEmail,
'users': oKey.users || [sEmail],
'hash': oKey.id.substr(-8).toUpperCase(),
'key': oKey
});
@ -378,16 +392,19 @@
if (aRec && 0 < aRec.length)
{
this.encryptKeys(_.compact(_.map(aRec, function (sEmail) {
this.encryptKeys(_.uniq(_.compact(_.map(aRec, function (sEmail) {
var oKey = PgpStore.findPublicKeyByEmailNotNative(sEmail) || null;
return {
'empty': !oKey,
'selected': ko.observable(!!oKey),
'user': oKey ? (oKey.user || sEmail) : sEmail,
'removable': oIdentity && oIdentity.email() && oIdentity.email() !== sEmail,
'users': oKey ? (oKey.users || [sEmail]) : [sEmail],
'hash': oKey ? oKey.id.substr(-8).toUpperCase() : '',
'key': oKey
};
})));
})), function (oEncryptKey) {
return oEncryptKey.hash;
}));
if (0 < this.encryptKeys().length)
{
@ -400,4 +417,4 @@
module.exports = ComposeOpenPgpPopupView;
}());
}());

View file

@ -184,4 +184,4 @@ _.delay(_.bind(function() {
module.exports = MessageOpenPgpPopupView;
}());
}());

View file

@ -27,12 +27,16 @@
No private key found
</div>
<div class="key-list-wrp" data-bind="visible: signKey">
<div class="key-list__item">
<div class="key-list__item-hash">
<div class="key-list__item row-fluid">
<div class="key-list__item-hash span4">
(<span data-bind="text: signKey() ? signKey().hash : ''"></span>)
</div>
<div class="key-list__item-name">
<span data-bind="text: signKey() ? signKey().user : ''"></span>
<div class="key-list__item-names span8">
<div data-bind="if: signKey()">
<div data-bind="foreach: signKey().users">
<div class="key-list__item-name" data-bind="text: $data"></div>
</div>
</div>
</div>
</div>
</div>
@ -54,18 +58,22 @@
</div>
<div class="key-list-wrp" data-bind="visible: encryptKeys() && encryptKeys().length > 0">
<div data-bind="foreach: encryptKeys">
<div class="key-list__item">
<div class="key-list__item-delete" data-bind="click: $parent.deletePublickKey">
<div class="key-list__item row-fluid">
<div class="key-list__item-delete span1" data-bind="click: removable ? $parent.deletePublickKey : null, css: {'disabled': !removable}">
<i class="icon-trash"></i>
</div>
<div class="key-list__item-hash" data-bind="visible: !empty">
<div class="key-list__item-hash span3" data-bind="visible: !empty">
(<span data-bind="text: hash"></span>)
</div>
<div class="key-list__item-name" data-bind="css: {'empty': empty}">
<span data-bind="text: user"></span>
</div>
<div class="key-list__item-error" data-bind="visible: empty">
(Public key not found)
<div class="span8">
<span class="key-list__item-names" data-bind="css: {'empty': empty}">
<span data-bind="foreach: users">
<div class="key-list__item-name" data-bind="text: $data"></div>
</span>
</span>
<span class="key-list__item-error" data-bind="visible: empty">
(Public key not found)
</span>
</div>
</div>
</div>
@ -78,19 +86,20 @@
<input type="password" class="inputPassword input-block-level i18n"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
data-i18n="[placeholder]POPUPS_COMPOSE_OPEN_PGP/LABEL_PASSWORD"
data-bind="visible: sign, value: password, hasfocus: password.focus, onEnter: doCommand" />
data-bind="visible: sign, textInput: password, hasfocus: password.focus, onEnter: doCommand" />
</div>
<div class="span7" data-bind="visible: encrypt() && 0 < publicKeysOptions().length">
<div class="form-inline">
<select class="input-block-level" data-bind="options: publicKeysOptions, value: selectedPublicKey,
optionsCaption: optionsCaption, optionsText: 'name', optionsValue: 'id',
optionsAfterRender: defautOptionsAfterRender"></select>
optionsAfterRender: addOptionClass"></select>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn buttonDo" data-bind="command: doCommand, hasfocus: buttonFocus">
<button class="btn buttonDo" data-bind="command: doCommand, hasfocus: buttonFocus,
enable: (sign() || encrypt()) && (!sign() || (sign() && password().length)) && (!encrypt() || encrypt() && encryptKeys().length > 0)">
<i data-bind="css: {'icon-key': !submitRequest(), 'icon-spinner animated': submitRequest()}"></i>
&nbsp;&nbsp;
<span class="i18n" data-bind="visible: sign() && !encrypt()" data-i18n="POPUPS_COMPOSE_OPEN_PGP/BUTTON_SIGN"></span>

View file

@ -18,11 +18,17 @@
<div class="controls key-list" data-bind="foreach: privateKeys" style="margin-top: 5px">
<div class="key-list__item">
<i class="key-list__item__radio icon-radio-unchecked"></i>
<span class="key-list__item__name">
<span data-bind="text: user"></span>
&nbsp;
<span>[<span data-bind="text: id"></span>]</span>
</span>
<div class="key-list__item__names">
<span data-bind="foreach: users">
<div>
<span class="key-list__item__name">
<span data-bind="text: $data"></span>
&nbsp;
<span>[<span data-bind="text: $parent.id"></span>]</span>
</span>
</div>
</span>
</div>
</div>
<!--<div data-bind="json: $data"></div>-->
</div>
@ -31,13 +37,13 @@
<label class="i18n control-label" data-i18n="POPUPS_MESSAGE_OPEN_PGP/LABEL_PASSWORD"></label>
<div class="controls">
<input type="password" class="inputPassword input-xlarge" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="value: password, hasfocus: password.focus, onEnter: doCommand" />
data-bind="textInput: password, hasfocus: password.focus, onEnter: doCommand" />
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn buttonDo" data-bind="command: doCommand, hasfocus: buttonFocus">
<button class="btn buttonDo" data-bind="command: doCommand, hasfocus: buttonFocus, enable: password().length > 0">
<i data-bind="css: {'icon-key': !submitRequest(), 'icon-spinner animated': submitRequest()}"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n="POPUPS_MESSAGE_OPEN_PGP/BUTTON_DECRYPT"></span>

View file

@ -33,7 +33,11 @@
<span class="open-pgp-key-img i18n" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PRIVATE">
<i class="icon-lock"></i>
</span>
<span class="open-pgp-key-user" data-bind="text: user"></span>
<span class="open-pgp-key-user">
<!-- ko foreach: users -->
<div class="open-pgp-key-user-address" data-bind="text: $data"></div>
<!-- /ko -->
</span>
<a class="btn btn-small btn-small-small btn-danger pull-right button-delete button-delete-transitions" data-bind="css: {'delete-access': deleteAccess()}, click: function(oOpenPGP) { $root.deleteOpenPgpKey(oOpenPGP); }">
<span class="i18n" data-i18n="SETTINGS_OPEN_PGP/DELETING_ASK"></span>
</a>
@ -56,8 +60,13 @@
<span class="open-pgp-key-img i18n" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PUBLIC">
<i class="icon-key"></i>
</span>
<span class="open-pgp-key-user" data-bind="text: user"></span>
(<span class="open-pgp-key-id" data-bind="text: id"></span>)
<span class="open-pgp-key-user">
<!-- ko foreach: users -->
<div class="open-pgp-key-user-address">
<span data-bind="text: $data"></span> (<span class="open-pgp-key-id" data-bind="text: $parent.id"></span>)
</div>
<!-- /ko -->
</span>
<a class="btn btn-small btn-small-small btn-danger pull-right button-delete button-delete-transitions" data-bind="css: {'delete-access': deleteAccess()}, click: function(oOpenPGP) { $root.deleteOpenPgpKey(oOpenPGP); }">
<span class="i18n" data-i18n="SETTINGS_OPEN_PGP/DELETING_ASK"></span>
</a>
@ -76,4 +85,4 @@
<!-- /ko -->
</tbody>
</table>
</div>
</div>