Browse Source

P4TB-30 warn before lowering a conversation privacy

1.1.101
francesco 3 years ago
parent
commit
c1baabac87
14 changed files with 193 additions and 42 deletions
  1. +24
    -0
      DEVELOPMENT.md
  2. +1
    -1
      chrome/bootstrap.js
  3. +17
    -7
      chrome/content/modules/pEp.js
  4. +1
    -0
      chrome/content/options.css
  5. +15
    -6
      chrome/content/options.js
  6. +11
    -1
      chrome/content/options.xul
  7. +8
    -3
      chrome/content/p4tb.js
  8. +4
    -3
      chrome/content/pEpMimeEncrypt.js
  9. +81
    -3
      chrome/content/pepmessengercompose.js
  10. +2
    -2
      chrome/content/pepmsghdrview.js
  11. +6
    -1
      chrome/content/prefsFactory.js
  12. +9
    -3
      manual-and-test.md
  13. +9
    -6
      tests/messages.js
  14. +5
    -6
      tests/unit/controller.js

+ 24
- 0
DEVELOPMENT.md View File

@ -0,0 +1,24 @@
## The `window` object
In `bootstrap.js` we subscribe to notifications from a `ww` service
that calls a `paint` method when the `window` object is globally
accessible as a global variable, for every open window.
From there we can pass it to all modules that want to access
it. Outside of this context we cannot assume that `window` is
accessible. An `unpaint` notification will be sent when the window is
closed.
## Updating the options
When we want to change the plugin preferences we can do so by
modifying three areas in the code:
- `options.xul` the markup, here you define the identifier for the option that will be used also elsewhere
- `prefsFactory.js` update getter and setter functions corresponding to the option
- `options.js` update `addAll`, the window load handler, the dialogAccept handler
## Compat and Prefs
Are defined in `options.js` and globally available

+ 1
- 1
chrome/bootstrap.js View File

@ -1,7 +1,7 @@
//TODO: Implement for restartless app https://developer.mozilla.org/en-US/docs/Archive/Add-ons/How_to_convert_an_overlay_extension_to_restartless#Step_9_bootstrap.js
/* globals Components, Services */
/* exported install, uninstall, startup, shutdown */
let {Services} = ChromeUtils.import('resource://gre/modules/Services.jsm');
let { Services } = ChromeUtils.import('resource://gre/modules/Services.jsm');
function loadStylesheets(styleSheets) {
// Load stylesheets


+ 17
- 7
chrome/content/modules/pEp.js View File

@ -19,7 +19,10 @@ const EXPORTED_SYMBOLS = ["pEp"];
const DIR_INCOMING = 0;
const DIR_OUTGOING = 1;
// refer to `typedef enum _PEP_enc_format` in `message.h` in
// the engine code for details about these values
const ENC_FORMAT_NONE = 0;
const ENC_FORMAT_S_MIME = 2;
const ENC_FORMAT_PGP_MIME = 3;
const ENC_FORMAT_PEP = 4;
const PEP_PREFIX = "pEp-";
@ -232,13 +235,13 @@ class pEp {
return obj && JSON.parse(JSON.stringify(obj));
}
let clone = new pEp.Message(source.id,
source.shortmsg,
source.longmsg,
deepCopy(source.from),
deepCopy(source.to),
source.dir,
source.enc_format,
deepCopy(source.attachments),
source.shortmsg,
source.longmsg,
deepCopy(source.from),
deepCopy(source.to),
source.dir,
source.enc_format,
deepCopy(source.attachments),
deepCopy(source.opt_fields));
source.cc && clone.setCc(source.cc);
source.bcc && clone.setBcc(source.bcc);
@ -430,6 +433,13 @@ pEp.Message = class {
let met = (this.enc_format === 0) ? this.fromUnencrypted.bind(this) : this.fromEncrypted.bind(this);
return met(atob, boundary);
}
isEncrypted() {
const formats = [ENC_FORMAT_S_MIME,
ENC_FORMAT_PGP_MIME,
ENC_FORMAT_PEP];
return formats.filter(format => format === this.enc_format).length > 0;
}
};


+ 1
- 0
chrome/content/options.css View File

@ -9,6 +9,7 @@ h3 {
.section {
margin-top: 0.1em;
margin-bottom: 0.1em;
max-width: 80ch;
}
.section .head {
margin-top: 1em;


+ 15
- 6
chrome/content/options.js View File

@ -1,9 +1,14 @@
// see developer.thunderbird.net/add-ons/updates/tb68
Preferences.addAll([
{ id: "extensions.p4tb.storeAllSecurely", type: "bool" },
// examples with different types
// { id: "extensions.nameOfAddon.pref2", type: "string" },
// { id: "extensions.nameOfAddon.pref3", type: "int" },
{ id: "extensions.p4tb.warnUnencrypted", type: "bool" },
{ id: "extensions.p4tb.unprotectedSubjects", type: "bool" },
{ id: "extensions.p4tb.storeProtectedOptions", type: "bool" },
{ id: "extensions.p4tb.securityLossWarning", type: "bool" },
{ id: "extensions.p4tb.pEpSync", type: "bool" },
// examples with different types
//{ id: "extensions.nameOfAddon.pref2", type: "string" },
//{ id: "extensions.nameOfAddon.pref3", type: "int" },
]);
const Compat = ChromeUtils.import("chrome://p4t/content/compatFactory.js")
@ -13,7 +18,11 @@ const Prefs = ChromeUtils.import("chrome://p4t/content/prefsFactory.js")
window.addEventListener('load', function () {
document.getElementById('storeAllSecurely').checked = Prefs.isStoreAllSecurely();
});
window.addEventListener('dialogaccept', function () {
Prefs.setIsStoreAllSecurely(document.getElementById('storeAllSecurely').checked);
document.getElementById('warnUnencrypted').checked = Prefs.isWarnUnencrypted();
let acceptButton = document.querySelector('button#accept');
acceptButton.addEventListener('click', function () {
Prefs.setIsStoreAllSecurely(document.getElementById('storeAllSecurely').checked);
Prefs.setWarnUnencrypted(document.getElementById('warnUnencrypted').checked);
window.close();
});
});

+ 11
- 1
chrome/content/options.xul View File

@ -5,7 +5,6 @@
<?xml-stylesheet type="text/css" href="chrome://p4t/content/pEp.css"?>
<?xml-stylesheet type="text/css" href="chrome://messenger/skin/messenger.css"?>
<window id="appPreferences"
buttons="accept"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="pEp Preferences">
@ -21,6 +20,7 @@
<tabpanels>
<tabpanel>
<vbox>
<!--
<html:h3>Account Security</html:h3>
<groupbox>
<hbox class="groupbox-title">
@ -30,13 +30,23 @@
<checkbox disabled="true" id="" preference="extensions.p4tb." label="Store messages securely" tooltiptext="" />
<checkbox disabled="true" id="" preference="extensions.p4tb." label="Enable privacy protection" tooltiptext="" />
</groupbox>
-->
<groupbox>
<checkbox id="storeAllSecurely" preference="extensions.p4tb.storeAllSecurely" label="Store messages securely for all accounts" tooltiptext="Uncheck this if you trust the server to keep an unencrypted version of your mails" />
<checkbox id="warnUnencrypted" preference="extensions.p4tb.warnUnencrypted" label="Warn me when a conversation gets to a lower level of privacy" tooltiptext="p≡p will ask you for confirmation before sending a plain message in a conversation which was formerly encrypted" />
<checkbox disabled="true" id="unprotectedSubjects" preference="extensions.p4tb.unprotectedSubjects" label="Enable unprotected message subjects" tooltiptext="" />
<checkbox disabled="true" id="storeProtectedOptions" preference="extensions.p4tb.storeProtectedOptions" label="Show store protected options" tooltiptext="" />
<checkbox disabled="true" id="securityLossWarning" preference="extensions.p4tb.securityLossWarning" label="Show a warning message when a message loses security through reply or forward" tooltiptext="" />
<checkbox disabled="true" id="pEpSync" preference="extensions.p4tb.pEpSync" label="Enable p≡p Sync" tooltiptext="" />
</groupbox>
<hbox>
<html:button>
Cancel
</html:button>
<html:button id="accept">
Ok
</html:button>
</hbox>
</vbox>
</tabpanel>
<tabpanel>


+ 8
- 3
chrome/content/p4tb.js View File

@ -133,10 +133,14 @@ pEpController.messageToCompFields = (pEpMessage, compFields) => {
pEpController.mimeFromMessage = emitter.fromMessage.bind(emitter);
pEpController.addFactory = init
let factories = [];
let init = () => {
factories.push(new Factory(pEpMimeEncrypt.Handler));
function init() {
let factory = new Factory(pEpMimeEncrypt.Handler);
factories.push(factory);
return factory;
};
let shutdown = () => {
@ -158,6 +162,8 @@ class Factory {
if (outer) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
// actually the component expects `sMimeSecurityInfo`, we are
// not passing it at the moment
let newComponent = new this.component();
console.log(" CREATE INSTANCE ", this.component.prototype.contractID);
if (this.component.prototype.contractID === "@mozilla.org/messengercompose/composesecure;1") {
@ -197,5 +203,4 @@ function generateRandomString(numChars) {
return text;
};
init();
const EXPORTED_SYMBOLS = ["pEpController"];

+ 4
- 3
chrome/content/pEpMimeEncrypt.js View File

@ -4,7 +4,7 @@
"use strict";
const jsmime = ChromeUtils.import("resource:///modules/jsmime.jsm").jsmime;
const { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm");
const {classes: Cc, results: Cr, interfaces: Ci} = Components;
const maxBufferLen = 102400;
@ -25,7 +25,9 @@ function isMessageUriInPgpMime() {
return ("messageURI" in gPgpMimeObj);
}
function PgpMimeEncrypt(sMimeSecurityInfo) {
function PgpMimeEncrypt(originalMessage, sMimeSecurityInfo) {
this.originalMessage = originalMessage;
console.log("pEp PgpMimeEncrypt CONSTRUCTOR");
this.wrappedJSObject = this;
@ -380,7 +382,6 @@ PgpMimeEncrypt.prototype = {
let resultMessage = this.pEp.cloneMessage(resultObj);
this.outQueue = this.pEpController.mimeFromMessage(resultMessage);
} else if (requireEncryption) {
// Open Dialog and ask user to send mail unencrypted
throw Cr.NS_ERROR_FAILURE;
}


+ 81
- 3
chrome/content/pepmessengercompose.js View File

@ -2,6 +2,9 @@ console.log("pepmessengercompose.js");
var pEpController = ChromeUtils.import("chrome://p4t/content/p4tb.js").pEpController;
var Handshake = ChromeUtils.import("chrome://p4t/content/modules/handshake.js").Handshake;
//let Composer = ChromeUtils.import("chrome://p4t/content/modules/composer.js").Composer;
const helper = ChromeUtils.import("chrome://p4t/content/TbHelper.js").TbHelper;
const { compatFactory } = ChromeUtils.import("chrome://p4t/content/compatFactory.js");
const { prefsFactory } = ChromeUtils.import("chrome://p4t/content/prefsFactory.js");
// Abstract Thunderbird methods
var TbAbstraction = {
@ -84,11 +87,41 @@ var _parseAddress = (address) => {
};
var pEpComposer = {
init: function () {
init: function (window) {
console.log("pEpComposer: init()");
const kEncryptionPolicy_Always = 2;
this.window = window;
const compat = compatFactory(this.window);
this.prefs = prefsFactory(compat);
this.initPrivacyWarning();
// we do not wait for the promises above to be
// resolved. pEpComposer init is run when the compose window
// is open and privacy will be checked when the message is
// sent. A race condition is unlikely yet possible.
TbAbstraction.getCurrentIdentity().setIntAttribute("encryptionpolicy", 2);
},
initPrivacyWarning() {
let id = this.window.arguments[0].originalMsgURI;
if (typeof id == 'undefined') {
// these windows have no original, users are starting a
// new conversation
return Promise.reject();
} else {
let originalMail = helper.readMailFromURI(id);
return pEpController
.messageFromMIME(originalMail)
.then((message) => {
return pEpController
.getOngoingRating(
message.from,
message.to,
message.cc,
message.bcc)
})
.then((rating) => this.originalRating = rating );
}
},
destroy: function () {
@ -156,7 +189,52 @@ var pEpComposer = {
sendMessageListener: function (event) {
console.log("pepmessengercompose.js: compose-send-message()");
//TODO To be implemented
if (this.prefs.isWarnUnencrypted() && this.privacyLoss()) {
if (this.unencryptedDialog()) {
//throw "Message not sent due to privacy concerns";
event.preventDefault();
}
} else {
// the privacy warning is the only function for this
// handler at the moment
return;
}
},
unencryptedDialog() {
/*
open dialog and ask user to send mail unencrypted.
initially copied from the code for the empty subject
warning, `comm-central` codebase, file
`MsgComposeCommand.js`
*/
let flags = Services.prompt.BUTTON_TITLE_IS_STRING *
Services.prompt.BUTTON_POS_0 +
Services.prompt.BUTTON_TITLE_IS_STRING *
Services.prompt.BUTTON_POS_1;
let confirm = Services.prompt.confirmEx(
window,
"Privacy Reminder",
"Some recipients cannot decrypt our messages, do you want to send the message unencrypted?",
flags,
"Cancel Sending",
"Send Unencrypted",
null,
null,
{ value: 0 });
return (confirm === 0);
},
privacyLoss() {
let rating = getCurrentIdentity().getIntAttribute("pEpRating");
return private(this.originalRating) && !private(rating);
function private (rating) {
// starting from `PEP_rating_reliable` in
// `_PEP_rating` in the engine `message_api.h`
return rating > 5;
}
},
pEp_onRecipientsChanged: function () {
@ -196,4 +274,4 @@ window.addEventListener("compose-window-init", pEpComposer.initListener.bind(pEp
window.addEventListener("compose-from-changed", pEpComposer.fromChangedListener.bind(pEpComposer), true);
window.addEventListener('compose-send-message', pEpComposer.sendMessageListener.bind(pEpComposer), true);
pEpComposer.init();
pEpComposer.init(window);

+ 2
- 2
chrome/content/pepmsghdrview.js View File

@ -122,9 +122,10 @@ var ColumnOverlay = {
var pEpHdrView = {
init: function () {
init: function (window) {
console.debug("pEpHdrView: init()", this, window);
this.onLoadListener();
pEpController.addFactory();
},
destroy: function () {
@ -206,7 +207,6 @@ var pEpHdrView = {
MessageView.updateBody(panel, body);
MessageView.updateSubject(gFolderDisplay.selectedMessage, subject);
// Preferences is imported by columnOverlay
if (headers && !Prefs.isStoreAllSecurely()) {
headers.setStringProperty("pEpDecryptedMessage", body);


+ 6
- 1
chrome/content/prefsFactory.js View File

@ -1,6 +1,5 @@
function prefsFactory(Compat) {
function newVersion () {
const { Services } = ChromeUtils.import('resource://gre/modules/Services.jsm');
return Services.prefs.getBranch('extensions.p4tb.');
}
function oldVersion () {
@ -15,6 +14,12 @@ function prefsFactory(Compat) {
},
setIsStoreAllSecurely: (v) => {
return prefs.setBoolPref('storeAllSecurely', v);
},
isWarnUnencrypted: () => {
return prefs.getBoolPref('warnUnencrypted');
},
setWarnUnencrypted: (v) => {
return prefs.setBoolPref('warnUnencrypted', v);
}
}
}


+ 9
- 3
manual-and-test.md View File

@ -16,11 +16,17 @@ No bar will be shown
# Options
You can access pEp options by clicking on the Thunderbird menu ->
You can access pEp options by clicking on the Thunderbird menu which
looks like three bars on the top right of the window. Then click on
Addons -> pEp for Thunderbird
## Trusted server
## Store messages securely
When this option is checked, your mail will be saved unencrypted on
When this option is not checked, your mail will be saved unencrypted on
your mail server
## Warn if the conversation privacy is lowering
When you reply or forward a private message to untrusted receivers,
the conversation will continue without encryption. In this case you
will see a warning when this option is set

+ 9
- 6
tests/messages.js View File

@ -1,6 +1,9 @@
let btoa = require('btoa');
let pEp = require('../chrome/content/modules/pEp');
let c = pEp.cloneMessage;
module.exports = {
encrypted: {
encrypted: c({
dir: 1,
id: 'pEp....@pep.security',
shortmsg: 'p≡p',
@ -36,17 +39,17 @@ module.exports = {
],
opt_fields: [ { key: 'X-pEp-Version', value: '2.1' } ],
enc_format: 3
},
}),
unencrypted: {
simple: {
simple: c({
shortmsg: 'pep test',
longmsg: 'body',
from: { address: 'fo@pep.security', comm_type: 0 },
to: [ { address: 'focchi.pinti@gmail.com', comm_type: 0 } ],
dir: 1,
enc_format: 0
},
withAttachments: {
}),
withAttachments: c({
shortmsg: 'pep attach',
longmsg: 'body',
attachments: [
@ -61,6 +64,6 @@ module.exports = {
to: [ { address: 'focchi.pinti@gmail.com', comm_type: 0 } ],
dir: 1,
enc_format: 0
}
})
}
};

+ 5
- 6
tests/unit/controller.js View File

@ -4,8 +4,8 @@ let chai = require('chai').use(require('chai-as-promised'));
let getQueue = require('../mock').getQueue;
let getController = require('../boilerplate').getController;
let expect = require('chai').expect
let messages = require('../messages.js');
chai.should();
let messages = require('../messages.js');
let expectations = {
encrypted: JSON.parse('[{"headers":[["Message-ID","pEp....@pep.security"],["Subject","p≡p"],["X-pEp-Version", "2.1"],["Content-Type","multipart/encrypted; protocol=\\"application/pgp-encrypted\\"; boundary=\\"boundary\\""]],"body":"this message was encrypted with p≡p https://pEp-project.org"},{"headers":[["Content-Type","application/pgp-encrypted"],["Content-Description","PGP/MIME version identification"]],"body":"Version: 1"},{"headers":[["Content-Type","application/octet-stream; name=\\"msg.asc\\""],["Content-Description","OpenPGP encrypted message"],["Content-Disposition","inline; filename=\\"msg.asc\\""]],"body":"1337"}]'),
@ -124,10 +124,9 @@ describe('controller calls', () => {
describe('message transformation', () => {
let atob = require('atob');
let boundary = 'boundary';
let controllerModule = require('../../chrome/content/modules/pEp');
it('produces common headers for all messages', () => {
expect(controllerModule.cloneMessage(messages.encrypted).commonHeaders())
expect(messages.encrypted.commonHeaders())
.to.deep.equal([[
"Message-ID", "pEp....@pep.security"
], [
@ -137,15 +136,15 @@ describe('controller calls', () => {
]]);
});
it('transforms an encrypted message', () => {
expect(controllerModule.cloneMessage(messages.encrypted).fromEncrypted(atob, boundary))
expect(messages.encrypted.fromEncrypted(atob, boundary))
.to.deep.equal(expectations.encrypted);
});
it('transforms an unencrypted message', () => {
expect(controllerModule.cloneMessage(messages.unencrypted.simple).fromUnencrypted(atob, boundary))
expect(messages.unencrypted.simple.fromUnencrypted(atob, boundary))
.to.deep.equal(expectations.unencrypted.simple);
});
it('transforms a multipart unencrypted message', () => {
expect(controllerModule.cloneMessage(messages.unencrypted.withAttachments).fromUnencrypted(atob, boundary))
expect(messages.unencrypted.withAttachments.fromUnencrypted(atob, boundary))
.to.deep.equal(expectations.unencrypted.withAttachments);
});
});


Loading…
Cancel
Save