Pgp improvements

This commit is contained in:
RainLoop Team 2015-07-29 21:21:24 +04:00
parent 3849fd02b4
commit 5123841540
18 changed files with 397 additions and 80 deletions

View file

@ -74,9 +74,7 @@
// Audio.prototype.record = function ()
// {
// this.getUserMedia({audio:true}, function () {
// window.console.log(arguments);
// }, function(oError) {
// window.console.log(arguments);
// });
// };

View file

@ -1,4 +1,6 @@
/* global RL_COMMUNITY */
(function () {
'use strict';

View file

@ -10,6 +10,7 @@
ko = require('ko'),
Enums = require('Common/Enums'),
Utils = require('Common/Utils'),
Globals = require('Common/Globals')
;
@ -24,6 +25,8 @@
this.trigger = ko.observable(false);
this.i18n = _.bind(this.i18n, this);
this.init();
}
Translator.prototype.data = {};
@ -293,29 +296,41 @@
{
var
self = this,
$html = $('html'),
fEmptyFunction = function () {},
iStart = (new Date()).getTime()
;
$html.addClass('rl-changing-language');
Globals.$html.addClass('rl-changing-language');
$.ajax({
'url': require('Common/Links').langLink(sLanguage, bAdmin),
'dataType': 'script',
'cache': true
})
.fail(fFail || fEmptyFunction)
.fail(fFail || Utils.emptyFunction)
.done(function () {
_.delay(function () {
self.reloadData();
(fDone || fEmptyFunction)();
$html.removeClass('rl-changing-language');
(fDone || Utils.emptyFunction)();
Globals.$html
.removeClass('rl-changing-language')
.removeClass('rl-rtl rl-ltr')
.addClass(-1 < Utils.inArray(sLanguage, ['ar', 'he', 'ur']) ? 'rl-rtl' : 'rl-ltr')
// .attr('dir', -1 < Utils.inArray(sLanguage, ['ar', 'he', 'ur']) ? 'rtl' : 'ltr')
;
}, 500 < (new Date()).getTime() - iStart ? 1 : 500);
})
;
};
Translator.prototype.init = function ()
{
Globals.$html.addClass('rl-' + (Globals.$html.attr('dir') || 'ltr'));
};
module.exports = new Translator();
}());

View file

@ -852,7 +852,11 @@
},
convertPre = function () {
return (arguments && 1 < arguments.length) ? arguments[1].toString().replace(/[\n]/gm, '<br />') : '';
return (arguments && 1 < arguments.length) ?
arguments[1].toString()
.replace(/[\n]/gm, '<br />')
.replace(/[\r]/gm, '')
: '';
},
fixAttibuteValue = function () {
@ -868,7 +872,7 @@
sText = sHtml
.replace(/\u0002([\s\S]*)\u0002/gm, '\u200C$1\u200C')
.replace(/<p[^>]*><\/p>/gi, '')
.replace(/<pre[^>]*>([\s\S\r\n]*)<\/pre>/gmi, convertPre)
.replace(/<pre[^>]*>([\s\S\r\n\t]*)<\/pre>/gmi, convertPre)
.replace(/[\s]+/gm, ' ')
.replace(/((?:href|data)\s?=\s?)("[^"]+?"|'[^']+?')/gmi, fixAttibuteValue)
.replace(/<br[^>]*>/gmi, '\n')

View file

@ -7,6 +7,7 @@
_ = require('_'),
Utils = require('Common/Utils'),
Translator = require('Common/Translator'),
AbstractInput = require('Component/AbstractInput')
;
@ -26,6 +27,12 @@
this.optionsText = oParams.optionsText || null;
this.optionsValue = oParams.optionsValue || null;
this.optionsCaption = oParams.optionsCaption || null;
if (this.optionsCaption)
{
this.optionsCaption = Translator.i18n(this.optionsCaption);
}
this.defautOptionsAfterRender = Utils.defautOptionsAfterRender;
}

View file

@ -16,7 +16,6 @@
{
this.oSettings = window['rainloopAppData'] || {};
this.oSettings = Utils.isNormal(this.oSettings) ? this.oSettings : {};
// window.console.log(this.oSettings);
}
SettingsStorage.prototype.oSettings = null;

View file

@ -526,6 +526,7 @@
var
bNew = false,
bIsHtml = false,
sTag = 'div',
bHasExternals = false,
bHasInternals = false,
oBody = null,
@ -584,9 +585,6 @@
bHasExternals = !!oData.Result.HasExternals;
bHasInternals = !!oData.Result.HasInternals;
oBody = $('<div id="' + sId + '" />').hide().addClass('rl-cache-class');
oBody.data('rl-cache-count', ++Globals.iMessageBodyCacheCount);
if (Utils.isNormal(oData.Result.Html) && '' !== oData.Result.Html)
{
bIsHtml = true;
@ -625,6 +623,10 @@
).html()
;
}
else
{
sResultHtml = '<pre>' + sResultHtml + '</pre>';
}
sPlain = '';
@ -633,12 +635,20 @@
oMessage.isPgpSigned(bPgpSigned);
oMessage.isPgpEncrypted(bPgpEncrypted);
}
else
{
sResultHtml = '<pre>' + sResultHtml + '</pre>';
}
}
else
{
bIsHtml = false;
sResultHtml = '<pre>' + sResultHtml + '</pre>';
}
oBody = $('<div id="' + sId + '" ></div>').hide().addClass('rl-cache-class');
oBody.data('rl-cache-count', ++Globals.iMessageBodyCacheCount);
oBody
.html(Utils.findEmailAndLinks(sResultHtml))
.addClass('b-text-part ' + (bIsHtml ? 'html' : 'plain'))
@ -753,8 +763,6 @@
{
if (sFolder && sUid)
{
window.console.log(sFolder, sUid);
this.message(this.staticMessage.populateByMessageListItem(null));
this.message().folderFullNameRaw = sFolder;
this.message().uid = sUid;

View file

@ -82,6 +82,28 @@
}), true));
};
/**
* @param {string} sEmail
* @return {?}
*/
PgpUserStore.prototype.findPublicKeyByEmailNotNative = function (sEmail)
{
return _.find(this.openpgpkeysPublic(), function (oItem) {
return oItem && sEmail === oItem.email;
}) || null;
};
/**
* @param {string} sEmail
* @return {?}
*/
PgpUserStore.prototype.findPrivateKeyByEmailNotNative = function (sEmail)
{
return _.find(this.openpgpkeysPrivate(), function (oItem) {
return oItem && sEmail === oItem.email;
}) || null;
};
/**
* @param {string} sEmail
* @param {string=} sPassword

View file

@ -19,6 +19,72 @@
}
}
.b-compose-open-pgp-content {
&.modal {
width: 700px;
}
.key-list {
background-color: #f9f9f9;
border-radius: 5px;
padding: 10px 15px;
margin-top: 10px;
min-height: 40px;
&-wrp {
overflow: hidden;
text-overflow: ellipsis;
&.empty {
text-align: center;
padding-top: 10px;
color: #aaa;
font-size: 16px;
}
}
&__item {
color: #333;
white-space: nowrap;
&-delete {
display: inline;
cursor: pointer;
margin-right: 5px;
}
&-name {
margin-right: 5px;
color: #333;
display: inline;
&.empty {
color: red;
}
}
&-error {
margin-right: 5px;
color: red;
display: inline;
}
&-hash {
margin-right: 5px;
color: #aaa;
display: inline;
}
}
}
.key-actions {
margin-top: 10px;
min-height: 40px;
}
}
.b-message-open-pgp-content {
&.modal {
width: 700px;

View file

@ -28,88 +28,137 @@
{
AbstractView.call(this, 'Popups', 'PopupsComposeOpenPgp');
var self = this;
this.optionsCaption = Translator.i18n('@i18n/Add a public key');
this.notification = ko.observable('');
this.sign = ko.observable(true);
this.encrypt = ko.observable(true);
this.sign = ko.observable(false);
this.encrypt = ko.observable(false);
this.password = ko.observable('');
this.password.focus = ko.observable(false);
this.buttonFocus = ko.observable(false);
this.from = ko.observable('');
this.to = ko.observableArray([]);
this.text = ko.observable('');
this.selectedPublicKey = ko.observable(null);
this.resultCallback = null;
this.signKey = ko.observable(null);
this.encryptKeys = ko.observableArray([]);
this.encryptKeysView = ko.computed(function () {
return _.compact(_.map(this.encryptKeys(), function (oKey) {
return oKey ? oKey.key : null;
}));
}, 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(-6) + ') ' + oKey.user,
'key': oKey
};
}));
});
this.submitRequest = ko.observable(false);
this.resultCallback = null;
// commands
this.doCommand = Utils.createCommand(this, function () {
var
self = this,
bResult = true,
oPrivateKey = null,
aPrivateKeys = [],
aPublicKeys = []
;
this.submitRequest(true);
if (bResult && this.sign() && '' === this.from())
{
this.notification(Translator.i18n('PGP_NOTIFICATIONS/SPECIFY_FROM_EMAIL'));
bResult = false;
}
if (bResult && this.sign())
{
oPrivateKey = PgpStore.findPrivateKeyByEmail(this.from(), this.password());
if (!oPrivateKey)
if (!this.signKey())
{
this.notification(Translator.i18n('PGP_NOTIFICATIONS/SPECIFY_FROM_EMAIL'));
bResult = false;
}
else if (!this.signKey().key)
{
this.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PRIVATE_KEY_FOUND_FOR', {
'EMAIL': this.from()
'EMAIL': this.signKey().email
}));
bResult = false;
}
}
if (bResult && this.encrypt() && 0 === this.to().length)
{
this.notification(Translator.i18n('PGP_NOTIFICATIONS/SPECIFY_AT_LEAST_ONE_RECIPIENT'));
bResult = false;
if (bResult)
{
aPrivateKeys = this.signKey().key.getNativeKeys();
oPrivateKey = aPrivateKeys[0] || null;
try
{
if (oPrivateKey)
{
oPrivateKey.decrypt(Utils.pString(this.password()));
}
}
catch (e)
{
oPrivateKey = null;
}
if (!oPrivateKey)
{
this.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PRIVATE_KEY_FOUND'));
bResult = false;
}
}
}
if (bResult && this.encrypt())
{
aPublicKeys = [];
_.each(this.to(), function (sEmail) {
var aKeys = PgpStore.findPublicKeysByEmail(sEmail);
if (0 === aKeys.length && bResult)
{
self.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND_FOR', {
'EMAIL': sEmail
}));
if (0 === this.encryptKeys().length)
{
this.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND'));
bResult = false;
}
else if (this.encryptKeys())
{
aPublicKeys = [];
_.each(this.encryptKeys(), function (oKey) {
if (oKey && oKey.key)
{
aPublicKeys = aPublicKeys.concat(_.compact(_.flatten(oKey.key.getNativeKeys())));
}
else if (oKey && oKey.email)
{
self.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND_FOR', {
'EMAIL': oKey.email
}));
bResult = false;
}
});
if (bResult && (0 === aPublicKeys.length || this.encryptKeys().length !== aPublicKeys.length))
{
bResult = false;
}
aPublicKeys = aPublicKeys.concat(aKeys);
});
if (bResult && (0 === aPublicKeys.length || this.to().length !== aPublicKeys.length))
{
bResult = false;
}
}
_.delay(function () {
if (bResult && self.resultCallback)
{
_.delay(function () {
if (self.resultCallback && bResult)
{
var oPromise = null;
try
{
if (oPrivateKey && 0 === aPublicKeys.length)
@ -154,17 +203,56 @@
}));
}
}
}
self.submitRequest(false);
self.submitRequest(false);
}, 10);
}, 10);
}
return bResult;
}, function () {
return !this.submitRequest() && (this.sign() || this.encrypt());
});
this.addCommand = Utils.createCommand(this, function () {
var
sKeyId = this.selectedPublicKey(),
aKeys = this.encryptKeys(),
oOption = sKeyId ? _.find(this.publicKeysOptions(), function (oItem) {
return oItem && sKeyId === oItem.id;
}) : null
;
if (oOption)
{
aKeys.push({
'empty': !oOption.key,
'selected': ko.observable(!!oOption.key),
'user': oOption.key.user,
'hash': oOption.key.id.substr(-6),
'key': oOption.key
});
this.encryptKeys(aKeys);
}
}, function () {
return !this.submitRequest() && this.selectedPublicKey();
});
this.selectedPublicKey.subscribe(function (sValue) {
if (sValue)
{
this.addCommand();
}
}, this);
this.sDefaultKeyScope = Enums.KeyState.PopupComposeOpenPGP;
this.defautOptionsAfterRender = Utils.defautOptionsAfterRender;
this.deletePublickKey = _.bind(this.deletePublickKey, this);
kn.constructorEnd(this);
}
@ -172,16 +260,24 @@
kn.extendAsViewModel(['View/Popup/ComposeOpenPgp', 'PopupsComposeOpenPgpViewModel'], ComposeOpenPgpPopupView);
_.extend(ComposeOpenPgpPopupView.prototype, AbstractView.prototype);
ComposeOpenPgpPopupView.prototype.deletePublickKey = function (oKey)
{
this.encryptKeys.remove(oKey);
};
ComposeOpenPgpPopupView.prototype.clearPopup = function ()
{
this.notification('');
this.sign(false);
this.encrypt(false);
this.password('');
this.password.focus(false);
this.buttonFocus(false);
this.from('');
this.to([]);
this.signKey(null);
this.encryptKeys([]);
this.text('');
this.submitRequest(false);
@ -231,6 +327,8 @@
var
aRec = [],
sEmail = '',
oKey = null,
oEmail = new EmailModel()
;
@ -258,8 +356,44 @@
return '' === oEmail.email ? false : oEmail.email;
}));
this.from(oIdentity ? oIdentity.email() : '');
this.to(aRec);
if (oIdentity && oIdentity.email())
{
sEmail = oIdentity.email();
oKey = PgpStore.findPrivateKeyByEmailNotNative(sEmail);
if (oKey)
{
this.signKey({
'user': oKey.user || sEmail,
'hash': oKey.id.substr(-6),
'key': oKey
});
}
}
if (this.signKey())
{
this.sign(true);
}
if (aRec && 0 < aRec.length)
{
this.encryptKeys(_.compact(_.map(aRec, function (sEmail) {
var oKey = PgpStore.findPublicKeyByEmailNotNative(sEmail) || null;
return {
'empty': !oKey,
'selected': ko.observable(!!oKey),
'user': oKey ? (oKey.user || sEmail) : sEmail,
'hash': oKey ? oKey.id.substr(-6) : '',
'key': oKey
};
})));
if (0 < this.encryptKeys().length)
{
this.encrypt(true);
}
}
this.text(sText);
};

View file

@ -47,6 +47,7 @@ var
plumber = require('gulp-plumber'),
gulpif = require('gulp-if'),
eol = require('gulp-eol'),
livereload = require('gulp-livereload'),
gutil = require('gulp-util')
;
@ -242,6 +243,7 @@ gulp.task('css:main-begin', ['less:main'], function() {
// .pipe(csslint.reporter())
.pipe(eol('\n', true))
.pipe(gulp.dest(cfg.paths.staticCSS))
.pipe(livereload())
;
});
@ -330,7 +332,7 @@ gulp.task('js:webpack:clear', function() {
.pipe(require('gulp-rimraf')());
});
gulp.task('js:webpack', ['js:webpack:clear'], function(callback) {
gulp.task('js:webpack', [/*'js:webpack:clear'*/], function(callback) {
var
webpack = require('webpack'),
@ -678,12 +680,14 @@ gulp.task('owncloud+', ['package:community-off', 'owncloud-']);
//WATCH
gulp.task('watch', ['fast'], function() {
cfg.watch = true;
livereload.listen();
gulp.watch(cfg.paths.globjs, {interval: 1000}, ['js:app', 'js:admin']);
gulp.watch(cfg.paths.less.main.watch, {interval: 1000}, ['css:main']);
});
gulp.task('watch+', ['fast+'], function() {
cfg.watch = true;
livereload.listen();
gulp.watch(cfg.paths.globjs, {interval: 1000}, ['js:app', 'js:admin']);
gulp.watch(cfg.paths.less.main.watch, {interval: 1000}, ['css:main']);
});

View file

@ -73,6 +73,7 @@
"gulp-through": "~0.3.0",
"lodash": "~3.9.3",
"gulp-if": "~1.2.5",
"node-notifier": "~4.2.3"
"node-notifier": "~4.2.3",
"gulp-livereload": "~3.8.0"
}
}

View file

@ -104,6 +104,9 @@ LANG_BS = "Bosanski"
LANG_BS_BS = "Bosanski"
LANG_BS_BA = "Bosanski"
LANG_AR = "‏العربية‏"
LANG_AR_AR = "‏العربية‏"
[LANGS_NAMES_EN]
LANG_EN = "English"
LANG_EN_US = "English (US)"
@ -210,3 +213,6 @@ LANG_SR_RS = "Serbian"
LANG_BS = "Bosnian"
LANG_BS_BS = "Bosnian"
LANG_BS_BA = "Bosnian"
LANG_AR = "Arabic"
LANG_AR_AR = "Arabic"

View file

@ -266,7 +266,8 @@ class Service
'{{BaseAppOpenPgpScriptLink}}' => $aData['OpenPgpJsLink'],
'{{BaseAppMainCommonScriptLink}}' => $aData['AppJsCommonLink'],
'{{BaseAppMainScriptLink}}' => $aData['AppJsLink'],
'{{BaseDir}}' => \in_array($aData['Language'], array('ar', 'he', 'ur')) ? 'rtl' : 'ltr'
'{{BaseDir}}' => 'ltr'
// '{{BaseDir}}' => \in_array($aData['Language'], array('ar', 'he', 'ur')) ? 'rtl' : 'ltr'
);
$aTemplateParameters['{{BaseHash}}'] = \md5(

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html class="no-js rl-booted-trigger rl-started-trigger">
<html class="no-js rl-booted-trigger rl-started-trigger" dir="{{BaseDir}}">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

View file

@ -4,7 +4,7 @@
&nbsp;&nbsp;
<!-- /ko -->
<select data-bind="options: options, value: value, enable: enable, optionsText: optionsText, optionsValue: optionsValue,
css: className, optionsAfterRender: defautOptionsAfterRender"></select>
optionsCaption: optionsCaption, css: className, optionsAfterRender: defautOptionsAfterRender"></select>
<!-- ko if: labeled -->
&nbsp;
<span class="i18n" data-bind="attr: {'data-i18n': label}"></span>

View file

@ -8,34 +8,83 @@
</h3>
</div>
<div class="modal-body">
<div class="form-horizontal">
<div class="alert" data-bind="visible: '' !== notification()">
<span data-bind="text: notification"></span>
</div>
<br />
<div class="control-group">
<div class="controls">
<div data-bind="component: {
<div class="alert" data-bind="visible: '' !== notification()">
<span data-bind="text: notification"></span>
</div>
<div class="row-fluid">
<div class="span5">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'POPUPS_COMPOSE_OPEN_PGP/LABEL_SIGN',
value: sign
}
}"></div>
<div data-bind="component: {
<div class="key-list" data-bind="visible: sign">
<div class="key-list-wrp empty" data-bind="visible: !signKey()">
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">
(<span data-bind="text: signKey() ? signKey().hash : ''"></span>)
</div>
<div class="key-list__item-name">
<span data-bind="text: signKey() ? signKey().user : ''"></span>
</div>
</div>
</div>
</div>
</div>
<div class="span7">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'POPUPS_COMPOSE_OPEN_PGP/LABEL_ENCRYPT',
value: encrypt
}
}"></div>
<div class="key-list" data-bind="visible: encrypt">
<div class="key-list-wrp empty" data-bind="visible: !encryptKeys() || encryptKeys().length === 0">
No public keys selected
</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">
<i class="icon-trash"></i>
</div>
<div class="key-list__item-hash" 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>
</div>
</div>
</div>
</div>
</div>
<div class="control-group">
<label class="i18n control-label" data-i18n="POPUPS_COMPOSE_OPEN_PGP/LABEL_PASSWORD"></label>
<div class="controls">
<input type="password" class="inputPassword input-large" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="value: password, hasfocus: password.focus, onEnter: doCommand" />
</div>
<div class="row-fluid key-actions">
<div class="span5">
<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" />
</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>
</div>
</div>
</div>

View file

@ -17,6 +17,7 @@
.flag.flag-en-uk {background-position: -176px -44px}
.flag.flag-en-ca {background-position: -48px -22px}
.flag.flag-ar, .flag.flag-ar-ar {background-position: -160px 0}
.flag.flag-bg, .flag.flag-bg-bg {background-position: -80px -11px}
.flag.flag-nl, .flag.flag-nl-nl {background-position: -80px -110px}
.flag.flag-pl, .flag.flag-pl-pl {background-position: -32px -121px}