Commit 8a9316fb authored by paz's avatar paz

Merge branch 'wip-429-be-more-precise-on-found-keys' into 'master'

fix #429 - say how many keys were found and how many were usable

Closes #343, #265, #392, and #429

See merge request !309
parents f94bc27d 6187df38
Pipeline #35334 failed with stages
in 13 minutes and 49 seconds
......@@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
* New option for lists to include their public keys in the headers of outgoing emails (conforming with Autocrypt, https://autocrypt.org/). Defaults to true. (#335)
* Add visual separator (78 dashes) to the end of the 'pseudoheaders' block: This should help users of Apple Mail, which jams this block and the body together. Hopefully, this change makes it easier to dinstiguish both parts from each other. (#348)
* `deliver_selfsent` per-list option to control whether subscribers get a copy of mail they sent themselves. (#365)
* Wrap pseudo headers if longer than 78 characters.
### Fixed
......@@ -21,6 +22,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
* Add missing List-Id header to notification mails sent to admins. This should help with filtering such messages, which is currently not easy to do in a reliable way.
* Fix running Schleuder with ruby 2.7.
* Ensure that GnuPG never asks for a passphrase, even if it wants one. (#448)
* Be more precise about how many keys are in the keyring and how many are usable, when resending (#429)
* Make it more clear what happens when resending an encrypted email fails (due to missing or too many matching keys), but falling back to unencrypted resend is allowed. (#343)
* Be more explicit that resending to other CC recipients has been aborted (#265)
## [3.4.1] / 2019-09-16
......
......@@ -17,6 +17,7 @@ module Mail
attr_accessor :original_message
attr_accessor :list
attr_accessor :protected_headers_subject
attr_writer :dynamic_pseudoheaders
# TODO: This should be in initialize(), but I couldn't understand the
# strange errors about wrong number of arguments when overriding
......@@ -44,9 +45,7 @@ module Mail
# might be gone (e.g. request-keywords that delete subscriptions or
# keys).
new.signer
self.dynamic_pseudoheaders.each do |str|
new.add_pseudoheader(str)
end
new.dynamic_pseudoheaders = self.dynamic_pseudoheaders.dup
# Store previously protected subject for later access.
# mail-gpg pulls headers from the decrypted mime parts "up" into the main
......@@ -270,20 +269,17 @@ module Mail
end
def add_pseudoheader(string_or_key, value=nil)
@dynamic_pseudoheaders ||= []
if value.present?
@dynamic_pseudoheaders << make_pseudoheader(string_or_key, value)
else
@dynamic_pseudoheaders << string_or_key.to_s
end
dynamic_pseudoheaders << make_pseudoheader(string_or_key, value)
end
def make_pseudoheader(key, value)
"#{key.to_s.camelize}: #{value.to_s}"
output = "#{key.to_s.camelize}: #{value.to_s}"
# wrap lines after 76 with 2 indents
output.gsub(/(.{1,76})( +|$)\n?/, " \\1\n").chomp.lstrip
end
def dynamic_pseudoheaders
@dynamic_pseudoheaders || []
@dynamic_pseudoheaders ||= []
end
def signature_state
......
......@@ -56,6 +56,9 @@ module Schleuder
# Only continue if all recipients are still here.
if recip_map.size < arguments.size
recip_map.keys.each do |aborted_sender|
mail.add_pseudoheader(:error, I18n.t("plugins.resend.aborted", email: aborted_sender))
end
return
end
......@@ -117,22 +120,22 @@ module Schleuder
Array(recipients).inject({}) do |hash, email|
keys = mail.list.keys(email)
# Exclude unusable keys.
keys.select! { |key| key.usable_for?(:encrypt) }
case keys.size
usable_keys = keys.select { |key| key.usable_for?(:encrypt) }
case usable_keys.size
when 1
hash[email] = keys.first
hash[email] = usable_keys.first
when 0
if encrypted_only
# Don't add the email to the result to exclude it from the
# recipients.
add_keys_error(mail, email, keys.size)
add_resend_msg(mail, email, :error, 'not_resent_no_keys', usable_keys.size, keys.size)
else
hash[email] = ''
end
else
# Always report this situation, regardless of sending or not. It's
# bad and should be fixed.
add_keys_error(mail, email, keys.size)
add_resend_msg(mail, email, :notice, 'not_resent_encrypted_no_keys', usable_keys.size, keys.size)
if ! encrypted_only
hash[email] = ''
end
......@@ -152,8 +155,8 @@ module Schleuder
gpg_opts
end
def self.add_keys_error(mail, email, keys_size)
mail.add_pseudoheader(:error, I18n.t("plugins.resend.not_resent_no_keys", email: email, num_keys: keys_size))
def self.add_resend_msg(mail, email, severity, msg, usable_keys_size, all_keys_size)
mail.add_pseudoheader(severity, I18n.t("plugins.resend.#{msg}", email: email, usable_keys: usable_keys_size, all_keys: all_keys_size))
end
def self.add_error_header(mail, recipients_map)
......@@ -163,15 +166,15 @@ module Schleuder
def self.add_resent_headers(mail, recipients_map, to_or_cc, sent_encrypted)
if sent_encrypted
prefix = I18n.t('plugins.resend.encrypted_to')
str = recipients_map.map do |email, key|
str = "\n" + recipients_map.map do |email, key|
"#{email} (#{key.fingerprint})"
end.join(', ')
end.join(",\n")
else
prefix = I18n.t('plugins.resend.unencrypted_to')
str = recipients_map.keys.join(', ')
str = ' ' + recipients_map.keys.join(", ")
end
headername = resent_header_name(to_or_cc)
mail.add_pseudoheader(headername, "#{prefix} #{str}")
mail.add_pseudoheader(headername, "#{prefix}#{str}")
end
def self.resent_header_name(to_or_cc)
......
......@@ -121,7 +121,9 @@ de:
Oder, um einen Schlüssel per HTTP von einem Server zu laden:
X-FETCH-KEY: https://example.org/keys/mykey.asc
resend:
not_resent_no_keys: Resending an <%{email}> fehlgeschlagen (%{num_keys} Schlüssel gefunden und unverschlüsseltes Senden verboten).
not_resent_no_keys: Resending an <%{email}> fehlgeschlagen (%{all_keys} Schlüssel gefunden, davon %{usable_keys} nutzbar. Unverschlüsseltes Senden verboten).
not_resent_encrypted_no_keys: Verschlüsseltes Resending an <%{email}> fehlgeschlagen (%{all_keys} Schlüssel gefunden, davon %{usable_keys} nutzbar).
aborted: Resending an <%{email}> abgebrochen aufgrund anderer Probleme.
encrypted_to: Verschlüsselt an
unencrypted_to: Unverschlüsselt an
invalid_recipient: "Ungültige Emailadresse für resend: %{address}"
......
......@@ -125,7 +125,9 @@ en:
Or, to fetch a key keys by URL:
X-FETCH-KEY: https://example.org/keys/mykey.asc
resend:
not_resent_no_keys: Resending to <%{email}> failed (%{num_keys} keys found and unencrypted sending disallowed).
not_resent_no_keys: Resending to <%{email}> failed (%{all_keys} keys found, of which %{usable_keys} can be used. Unencrypted sending not allowed).
not_resent_encrypted_no_keys: Resending as encrypted email to <%{email}> failed (%{all_keys} keys found, of which %{usable_keys} can be used).
aborted: Resending to <%{email}> aborted due to other errors.
encrypted_to: Encrypted to
unencrypted_to: Unencrypted to
invalid_recipient: "Invalid email-address for resending: %{address}"
......
......@@ -108,7 +108,7 @@ describe "running filters" do
expect(htmlmail.to).to eql(["admin@example.org"])
signed_parts = htmlmail.parts[0].parts
expect(signed_parts[0].body.to_s).to include("Note: This message included an alternating HTML-part that contained PGP-data. The HTML-part was removed to enable parsing the message more properly.")
expect(signed_parts[0].body.to_s).to include("Note: This message included an alternating HTML-part that contained\n PGP-data. The HTML-part was removed to enable parsing the message more\n properly.\n")
# why is this double wrapped?
expect(signed_parts[1].parts[0][:content_type].content_type).to eql("text/plain")
expect(signed_parts[1].parts[0].body.to_s).to eql("blabla\n")
......
This diff is collapsed.
......@@ -39,7 +39,7 @@ describe Schleuder::Filters do
expect(mail[:content_type].content_type).to eql("multipart/mixed")
expect(mail.parts.size).to be(1)
expect(mail.parts.first[:content_type].content_type).to eql("text/plain")
expect(mail.dynamic_pseudoheaders).to include("Note: This message included an alternating HTML-part that contained PGP-data. The HTML-part was removed to enable parsing the message more properly.")
expect(mail.dynamic_pseudoheaders).to include("Note: This message included an alternating HTML-part that contained\n PGP-data. The HTML-part was removed to enable parsing the message more\n properly.")
end
it "does NOT strip HTML-part from multipart/alternative-message that does NOT contain ascii-armored PGP-data" do
......@@ -91,7 +91,7 @@ describe Schleuder::Filters do
expect(mail[:content_type].content_type).to eql('multipart/mixed')
expect(mail.parts.size).to be(1)
expect(mail.parts.first[:content_type].content_type).to eql('text/plain')
expect(mail.dynamic_pseudoheaders).to include('Note: This message included keywords and an alternating HTML-part. The HTML-part was removed to prevent the disclosure of these keywords to third parties.')
expect(mail.dynamic_pseudoheaders).to include("Note: This message included keywords and an alternating HTML-part. The\n HTML-part was removed to prevent the disclosure of these keywords to third\n parties.")
end
it 'does NOT strip HTML-part from multipart/alternative-message that does NOT contain keywords' do
......
......@@ -149,5 +149,63 @@ describe Mail::Message do
expect(mail.parts.last.body.to_s).to eql(footer)
end
context "makes a pseudo header" do
it "with key / value" do
mail = Mail.new
ph = mail.make_pseudoheader('notice','some value')
expect(ph).to eql('Notice: some value')
end
it "without value" do
mail = Mail.new
ph = mail.make_pseudoheader(:key,nil)
expect(ph).to eql('Key: ')
end
it "with empty value" do
mail = Mail.new
ph = mail.make_pseudoheader(:key,'')
expect(ph).to eql('Key: ')
end
it "that is getting wrapped" do
mail = Mail.new
ph = mail.make_pseudoheader('notice','adds list#public_footer as last mime-part without changing its value adds list#public_footer as last mime-part without changing its value')
expect(ph).to eql("Notice: adds list#public_footer as last mime-part without changing its value\n adds list#public_footer as last mime-part without changing its value")
expect(ph.split("\n")).to all( satisfy{|l| l.length <= 78 })
end
it "that multiline are getting wrapped" do
mail = Mail.new
ph = mail.make_pseudoheader('notice',"adds list#public_footer as last mime-part\nwithout changing its value adds list#public_footer as last mime-part without changing its value")
expect(ph).to eql("Notice: adds list#public_footer as last mime-part\n without changing its value adds list#public_footer as last mime-part without\n changing its value")
expect(ph.split("\n")).to all( satisfy{|l| l.length <= 78 })
end
it "that single multiline are getting indented" do
mail = Mail.new
ph = mail.make_pseudoheader('notice',"on line 1\non line 2 but indented")
expect(ph).to eql("Notice: on line 1\n on line 2 but indented")
expect(ph.split("\n")).to all( satisfy{|l| l.length <= 78 })
end
it "that a line with less than 76 gets wrapped" do
mail = Mail.new
ph = mail.make_pseudoheader('keylongerthan8', 'afafa afafaf' * 6) # message is 72 long
expect(ph).to eql("Keylongerthan8: afafa afafafafafa afafafafafa afafafafafa afafafafafa\n afafafafafa afafaf")
expect(ph.split("\n")).to all( satisfy{|l| l.length <= 78 })
end
it "that a multiline with less than 76 get wrapped correctly on the first line" do
mail = Mail.new
ph = mail.make_pseudoheader('keylongerthan8', ('afafa afafaf' * 6)+"\nbla bla newline")
expect(ph).to eql("Keylongerthan8: afafa afafafafafa afafafafafa afafafafafa afafafafafa\n afafafafafa afafaf\n bla bla newline")
expect(ph.split("\n")).to all( satisfy{|l| l.length <= 78 })
end
it "that a multiline with less than 76 get wrapped correctly on the first line and the followint lines" do
mail = Mail.new
ph = mail.make_pseudoheader('keylongerthan8', ('afafa afafaf' * 6)+"\nbla bla newline"+('afafa afafaf' * 6))
expect(ph).to eql("Keylongerthan8: afafa afafafafafa afafafafafa afafafafafa afafafafafa\n afafafafafa afafaf\n bla bla newlineafafa afafafafafa afafafafafa afafafafafa afafafafafa\n afafafafafa afafaf")
expect(ph.split("\n")).to all( satisfy{|l| l.length <= 78 })
end
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment