[DRE-commits] [schleuder] 01/02: New upstream version 3.0.0~beta12

Georg Faerber georg-alioth-guest at moszumanska.debian.org
Thu Dec 22 20:27:17 UTC 2016


This is an automated email from the git hooks/post-receive script.

georg-alioth-guest pushed a commit to branch master
in repository schleuder.

commit 69f4fac6c5b595292c347b6ce302de18bf07c8fc
Author: Georg Faerber <georg at riseup.net>
Date:   Thu Dec 22 21:26:29 2016 +0100

    New upstream version 3.0.0~beta12
---
 .gitattributes                           |   1 +
 CHANGELOG.md                             |  21 ++++
 Gemfile.lock                             |  17 +--
 README.md                                |   8 +-
 etc/init.d/schleuder-api-daemon          |  78 ++++++++++++
 gems/schleuder-3.0.0.beta10.gem          | Bin 39424 -> 0 bytes
 gems/schleuder-3.0.0.beta10.gem.sig      | Bin 587 -> 0 bytes
 gems/schleuder-3.0.0.beta10.tar.gz       | Bin 688196 -> 0 bytes
 gems/schleuder-3.0.0.beta10.tar.gz.sig   | Bin 587 -> 0 bytes
 gems/schleuder-3.0.0.beta6.gem           | Bin 36864 -> 0 bytes
 gems/schleuder-3.0.0.beta6.gem.sig       | Bin 94 -> 0 bytes
 gems/schleuder-3.0.0.beta7.gem           | Bin 39424 -> 0 bytes
 gems/schleuder-3.0.0.beta7.gem.sig       | Bin 564 -> 0 bytes
 gems/schleuder-3.0.0.beta8.gem           | Bin 39424 -> 0 bytes
 gems/schleuder-3.0.0.beta8.gem.sig       | Bin 564 -> 0 bytes
 gems/schleuder-3.0.0.beta8.tar.gz        | Bin 154626 -> 0 bytes
 gems/schleuder-3.0.0.beta8.tar.gz.sig    | Bin 564 -> 0 bytes
 gems/schleuder-3.0.0.beta9.gem           | Bin 39424 -> 0 bytes
 gems/schleuder-3.0.0.beta9.gem.sig       | Bin 587 -> 0 bytes
 gems/schleuder-3.0.0.beta9.tar.gz        | Bin 325485 -> 0 bytes
 gems/schleuder-3.0.0.beta9.tar.gz.sig    | Bin 587 -> 0 bytes
 lib/schleuder/cli/cert.rb                |   3 +
 lib/schleuder/conf.rb                    |  53 ++++++---
 lib/schleuder/filters/send_key_filter.rb |   2 +-
 lib/schleuder/filters_runner.rb          |   2 +-
 lib/schleuder/list.rb                    |   4 +
 lib/schleuder/mail/message.rb            |  74 +++++++++---
 lib/schleuder/plugins/foo.rb             |   8 --
 lib/schleuder/plugins/resend.rb          | 179 ++++++++++++++++++++--------
 lib/schleuder/plugins/sign_this.rb       |   8 +-
 lib/schleuder/runner.rb                  |  47 ++++----
 lib/schleuder/subscription.rb            |   4 +-
 lib/schleuder/version.rb                 |   2 +-
 locales/de.yml                           |   7 +-
 locales/en.yml                           |   7 +-
 schleuder.gemspec                        |   1 +
 spec/factories/lists.rb                  |  39 ++++++
 spec/factories/subscriptions.rb          |   9 ++
 spec/fixtures/example_key.txt            |  52 ++++++++
 spec/gnupg/pubring.gpg~                  | Bin 2225 -> 0 bytes
 spec/schleuder/list_spec.rb              | 197 ++++++++++++++++---------------
 spec/schleuder/subscription_spec.rb      |  61 +++-------
 spec/spec_helper.rb                      |  30 ++++-
 43 files changed, 642 insertions(+), 272 deletions(-)

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..d547568
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+/gems/ export-ignore
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67bd161..ee074b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,27 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 The format of this file is based on [Keep a Changelog](http://keepachangelog.com/).
 
+## [3.0.0.beta12] / 2016-12-22
+
+### Changed
+
+ * Show file permission warning if cert is being generated as root.
+ * Use hard-coded defaults as base to merge config-file over.
+
+### Added
+
+ * New keyword `x-resend-cc` to send a message to multiple recipients that should know of each another. The ciphertext will be encrypted only once to all recipients, too.
+ * More specs.
+ * Skript for schleuder-api-daemon under sysvinit.
+
+### Fixed
+
+ * Fix tests for non-default listlogs_dir.
+ * Fix pseudo-header "Sig" for unknown keys.
+ * Fix adding subject_prefix_in for unencrypted messages.
+ * Fix checking permissions of listdir and list.log for newly created lists.
+ * Fix occasionally empty 'date'-pseudo-header.
+
 ## [3.0.0.beta11] / 2016-12-07
 
 ### Changed
diff --git a/Gemfile.lock b/Gemfile.lock
index 2a6e3b0..9fd5b03 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,7 @@
 PATH
   remote: .
   specs:
-    schleuder (3.0.0.beta11)
+    schleuder (3.0.0.beta12)
       activerecord (~> 4.1)
       mail-gpg (~> 0.2.7)
       rake (~> 10)
@@ -30,10 +30,12 @@ GEM
     arel (6.0.3)
     backports (3.6.8)
     builder (3.2.2)
-    daemons (1.2.3)
+    daemons (1.2.4)
     database_cleaner (1.5.3)
     diff-lcs (1.2.5)
-    eventmachine (1.2.0.1)
+    eventmachine (1.2.1)
+    factory_girl (4.7.0)
+      activesupport (>= 3.0.0)
     gpgme (2.0.12)
       mini_portile2 (~> 2.1.0)
     hirb (0.7.3)
@@ -41,7 +43,7 @@ GEM
     json (1.8.3)
     mail (2.6.4)
       mime-types (>= 1.16, < 4)
-    mail-gpg (0.2.8)
+    mail-gpg (0.2.9)
       gpgme (~> 2.0, >= 2.0.2)
       mail (~> 2.5, >= 2.5.3)
     mime-types (3.1)
@@ -81,11 +83,11 @@ GEM
       sinatra (~> 1.4.0)
       tilt (>= 1.3, < 3)
     sqlite3 (1.3.12)
-    thin (1.6.4)
+    thin (1.7.0)
       daemons (~> 1.0, >= 1.0.9)
       eventmachine (~> 1.0, >= 1.0.4)
-      rack (~> 1.0)
-    thor (0.19.1)
+      rack (>= 1, < 3)
+    thor (0.19.4)
     thread_safe (0.3.5)
     tilt (2.0.5)
     tzinfo (1.2.2)
@@ -96,6 +98,7 @@ PLATFORMS
 
 DEPENDENCIES
   database_cleaner
+  factory_girl
   hirb
   rspec (~> 3.5.0)
   schleuder!
diff --git a/README.md b/README.md
index 4772bb4..9dd86ef 100644
--- a/README.md
+++ b/README.md
@@ -42,15 +42,15 @@ Additionally these **rubygems** are required (will be installed automatically un
 Installing Schleuder
 ------------
 
-1. Download [the gem](https://git.codecoop.org/schleuder/schleuder3/raw/master/gems/schleuder-3.0.0.beta11.gem) and [the OpenPGP-signature](https://git.codecoop.org/schleuder/schleuder3/raw/master/gems/schleuder-3.0.0.beta11.gem.sig) and verify:
+1. Download [the gem](https://git.codecoop.org/schleuder/schleuder3/raw/master/gems/schleuder-3.0.0.beta12.gem) and [the OpenPGP-signature](https://git.codecoop.org/schleuder/schleuder3/raw/master/gems/schleuder-3.0.0.beta12.gem.sig) and verify:
    ```
    gpg --recv-key 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3
-   gpg --verify schleuder-3.0.0.beta11.gem.sig
+   gpg --verify schleuder-3.0.0.beta12.gem.sig
    ```
 
 2. If all went well install the gem:
    ```
-   gem install schleuder-3.0.0.beta11.gem
+   gem install schleuder-3.0.0.beta12.gem
    ```
 
 3. Set up schleuder:
@@ -118,4 +118,4 @@ GNU GPL 3.0. Please see [LICENSE.txt](LICENSE.txt).
 Alternative Download
 --------------------
 
-Alternatively to the gem-files you can download the latest release as [a tarball](https://git.codecoop.org/schleuder/schleuder3/raw/master/gems/schleuder-3.0.0.beta11.tar.gz) and [its OpenPGP-signature](https://git.codecoop.org/schleuder/schleuder3/raw/master/gems/schleuder-3.0.0.beta11.tar.gz.sig).
+Alternatively to the gem-files you can download the latest release as [a tarball](https://git.codecoop.org/schleuder/schleuder3/raw/master/gems/schleuder-3.0.0.beta12.tar.gz) and [its OpenPGP-signature](https://git.codecoop.org/schleuder/schleuder3/raw/master/gems/schleuder-3.0.0.beta12.tar.gz.sig).
diff --git a/etc/init.d/schleuder-api-daemon b/etc/init.d/schleuder-api-daemon
new file mode 100755
index 0000000..8918bc2
--- /dev/null
+++ b/etc/init.d/schleuder-api-daemon
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides:          schleuder-api-daemon
+# Required-Start:    $local_fs $network $syslog
+# Required-Stop:     $local_fs $network $syslog
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Schleuder API daemon
+# Description:       Schleuder API daemon — provides access for schleuder-cli and schleuder-web
+### END INIT INFO
+
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+
+. /lib/init/vars.sh
+
+. /lib/lsb/init-functions
+
+NAME=schleuder-api-daemon
+DAEMON=/usr/bin/schleuder-api-daemon
+PIDFILE=/var/run/$NAME.pid
+USER=schleuder
+GROUP=schleuder
+
+test -x $DAEMON || exit 5
+
+start() {
+  if [ -f /var/run/$NAME.pid ]; then
+        log_failure_msg "$NAME is running already, please stop it first"
+        exit 1
+  fi
+
+  if ! id $USER >/dev/null 2>&1; then
+        log_failure_msg "User \"$USER\" does not exist"
+        exit 1
+  fi
+
+  if ! getent group $GROUP >/dev/null 2>&1; then
+        log_failure_msg "Group \"$GROUP\" does not exist"
+        exit 1
+  fi
+
+  log_daemon_msg "Starting $NAME" "$DAEMON"
+  start-stop-daemon --chuid "$USER":"$GROUP" --pidfile $PIDFILE --make-pidfile --background --exec $DAEMON --start
+  log_end_msg $?
+}
+
+stop() {
+  if ! [ -f /var/run/$NAME.pid ]; then
+        log_failure_msg "$NAME isn't running currently, nothing to do"
+        exit 1
+  fi
+
+  log_daemon_msg "Stopping $NAME" "$DAEMON"
+  start-stop-daemon --oknodo --pidfile $PIDFILE --stop --retry 10
+  log_end_msg $?
+  rm $PIDFILE
+}
+
+restart() {
+  stop
+  start
+}
+
+case "$1" in
+  start)
+    start
+    ;;
+  stop)
+    stop
+    ;;
+  restart)
+    restart
+    ;;
+  *)
+    log_success_msg "Usage: $0 {start|stop|restart}"
+    exit 1
+esac
diff --git a/gems/schleuder-3.0.0.beta10.gem b/gems/schleuder-3.0.0.beta10.gem
deleted file mode 100644
index 854ba83..0000000
Binary files a/gems/schleuder-3.0.0.beta10.gem and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta10.gem.sig b/gems/schleuder-3.0.0.beta10.gem.sig
deleted file mode 100644
index 5ef90e6..0000000
Binary files a/gems/schleuder-3.0.0.beta10.gem.sig and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta10.tar.gz b/gems/schleuder-3.0.0.beta10.tar.gz
deleted file mode 100644
index 6438a91..0000000
Binary files a/gems/schleuder-3.0.0.beta10.tar.gz and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta10.tar.gz.sig b/gems/schleuder-3.0.0.beta10.tar.gz.sig
deleted file mode 100644
index 2d927de..0000000
Binary files a/gems/schleuder-3.0.0.beta10.tar.gz.sig and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta6.gem b/gems/schleuder-3.0.0.beta6.gem
deleted file mode 100644
index 1e76f62..0000000
Binary files a/gems/schleuder-3.0.0.beta6.gem and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta6.gem.sig b/gems/schleuder-3.0.0.beta6.gem.sig
deleted file mode 100644
index 2f0e1ab..0000000
Binary files a/gems/schleuder-3.0.0.beta6.gem.sig and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta7.gem b/gems/schleuder-3.0.0.beta7.gem
deleted file mode 100644
index ef156fa..0000000
Binary files a/gems/schleuder-3.0.0.beta7.gem and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta7.gem.sig b/gems/schleuder-3.0.0.beta7.gem.sig
deleted file mode 100644
index 166f687..0000000
Binary files a/gems/schleuder-3.0.0.beta7.gem.sig and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta8.gem b/gems/schleuder-3.0.0.beta8.gem
deleted file mode 100644
index f901cd6..0000000
Binary files a/gems/schleuder-3.0.0.beta8.gem and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta8.gem.sig b/gems/schleuder-3.0.0.beta8.gem.sig
deleted file mode 100644
index 0575b93..0000000
Binary files a/gems/schleuder-3.0.0.beta8.gem.sig and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta8.tar.gz b/gems/schleuder-3.0.0.beta8.tar.gz
deleted file mode 100644
index 9336f84..0000000
Binary files a/gems/schleuder-3.0.0.beta8.tar.gz and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta8.tar.gz.sig b/gems/schleuder-3.0.0.beta8.tar.gz.sig
deleted file mode 100644
index a7ed52e..0000000
Binary files a/gems/schleuder-3.0.0.beta8.tar.gz.sig and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta9.gem b/gems/schleuder-3.0.0.beta9.gem
deleted file mode 100644
index cd97de3..0000000
Binary files a/gems/schleuder-3.0.0.beta9.gem and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta9.gem.sig b/gems/schleuder-3.0.0.beta9.gem.sig
deleted file mode 100644
index 37079a3..0000000
Binary files a/gems/schleuder-3.0.0.beta9.gem.sig and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta9.tar.gz b/gems/schleuder-3.0.0.beta9.tar.gz
deleted file mode 100644
index abb09b6..0000000
Binary files a/gems/schleuder-3.0.0.beta9.tar.gz and /dev/null differ
diff --git a/gems/schleuder-3.0.0.beta9.tar.gz.sig b/gems/schleuder-3.0.0.beta9.tar.gz.sig
deleted file mode 100644
index fa6dae8..0000000
Binary files a/gems/schleuder-3.0.0.beta9.tar.gz.sig and /dev/null differ
diff --git a/lib/schleuder/cli/cert.rb b/lib/schleuder/cli/cert.rb
index 2b98ad1..ce5b8ee 100644
--- a/lib/schleuder/cli/cert.rb
+++ b/lib/schleuder/cli/cert.rb
@@ -10,6 +10,9 @@ module Schleuder
       puts "Fingerprint of generated certificate: #{fingerprint}"
       puts "Have this fingerprint included into the configuration-file of all clients that want to connect to your Schleuder API."
       puts "To activate TLS set `use_tls: true` in #{ENV['SCHLEUDER_CONFIG']} and restart schleuder-api-daemon."
+      if Process.euid == 0
+        puts "! Warning: this process was run as root — please make sure the above files are accessible by the user that is running `schleuder-api-daemon`."
+      end
     end
 
     desc 'fingerprint', 'Show fingerprint of configured certificate.'
diff --git a/lib/schleuder/conf.rb b/lib/schleuder/conf.rb
index da56984..7675ff7 100644
--- a/lib/schleuder/conf.rb
+++ b/lib/schleuder/conf.rb
@@ -4,12 +4,41 @@ module Schleuder
 
     EMAIL_REGEXP = /\A.+ at .+\z/i
 
-    def config
-      @config ||= self.class.load_config('schleuder', ENV['SCHLEUDER_CONFIG'])
-    end
+    DEFAULTS = {
+      'lists_dir' => '/var/schleuder/lists',
+      'listlogs_dir' => '/var/schleuder/lists',
+      'plugins_dir' => '/etc/schleuder/plugins',
+      'log_level' => 'warn',
+      'smtp_settings' => {
+        'address' => 'localhost',
+        'port' => 25,
+        'domain' => 'localhost',
+        'enable_starttls_auto' => true,
+        # Don't verify by default because most smtp servers don't include
+        # 'localhost' into their TLS-certificates.
+        'openssl_verify_mode' => 'none',
+        'authentication' => nil,
+        'user_name' => nil,
+        'password' => nil,
+      },
+      'database' => {
+        'production' => {
+          'adapter' =>  'sqlite3',
+          'database' => '/var/schleuder/db.sqlite'
+        }
+      },
+      'api' => {
+        'host' => 'localhost',
+        'port' => 4443,
+        'use_tls' => false,
+        'tls_cert_file' => '/etc/schleuder/schleuder-certificate.pem',
+        'tls_key_file' => '/etc/schleuder/schleuder-private-key.pem',
+        'valid_api_keys' => []
+      }
+    }
 
-    def self.load_config(defaults_basename, filename)
-      load_defaults(defaults_basename).deep_merge(load_config_file(filename))
+    def config
+      @config ||= load_config(ENV['SCHLEUDER_CONFIG'])
     end
 
     def self.lists_dir
@@ -81,7 +110,11 @@ module Schleuder
 
     private
 
-    def self.load_config_file(filename)
+    def load_config(filename)
+      DEFAULTS.deep_merge(load_config_file(filename))
+    end
+
+    def load_config_file(filename)
       file = Pathname.new(filename)
       if file.readable?
         YAML.load(file.read)
@@ -89,13 +122,5 @@ module Schleuder
         {}
       end
     end
-
-    def self.load_defaults(basename)
-      file = Pathname.new(ENV['SCHLEUDER_ROOT']).join("etc/#{basename}.yml")
-      if ! file.readable?
-        raise RuntimeError, "Error: '#{file}' is not a readable file."
-      end
-      load_config_file(file)
-    end
   end
 end
diff --git a/lib/schleuder/filters/send_key_filter.rb b/lib/schleuder/filters/send_key_filter.rb
index 3fbc845..c013859 100644
--- a/lib/schleuder/filters/send_key_filter.rb
+++ b/lib/schleuder/filters/send_key_filter.rb
@@ -19,7 +19,7 @@ module Schleuder
       out.attachments[filename].content_type = 'application/pgp-keys'
       out.attachments[filename].content_description = 'OpenPGP public key'
       # TODO: find out why the gpg-module puts all the headers into the first mime-part, too
-      out.gpg sign: true
+      out.gpg list.gpg_sign_options
       out.deliver
       exit
     end
diff --git a/lib/schleuder/filters_runner.rb b/lib/schleuder/filters_runner.rb
index 4dc4ced..cf59da0 100644
--- a/lib/schleuder/filters_runner.rb
+++ b/lib/schleuder/filters_runner.rb
@@ -69,7 +69,7 @@ module Schleuder
         reply.from = @list.email
         reply.return_path = @list.bounce_address
         reply.body = msg
-        gpg_opts = {sign: true}
+        gpg_opts = @list.gpg_sign_options
         if @list.keys("<#{sender_addr}>").present?
           logger.debug "Found key for address"
           gpg_opts[encrypt] = true
diff --git a/lib/schleuder/list.rb b/lib/schleuder/list.rb
index 4d7a087..21c6ccf 100644
--- a/lib/schleuder/list.rb
+++ b/lib/schleuder/list.rb
@@ -205,6 +205,10 @@ module Schleuder
       $stderr.puts "Failed to kill gpg-agent: #{e}"
     end
 
+    def gpg_sign_options
+      {sign: true, sign_as: self.fingerprint}
+    end
+
     def fingerprint=(arg)
       # Strip whitespace from incoming arg.
       if arg
diff --git a/lib/schleuder/mail/message.rb b/lib/schleuder/mail/message.rb
index e87161f..cd76934 100644
--- a/lib/schleuder/mail/message.rb
+++ b/lib/schleuder/mail/message.rb
@@ -11,6 +11,9 @@ module Mail
     def setup(recipient, list)
       if self.encrypted?
         new = self.decrypt(verify: true)
+        # Work around a bug in mail-gpg: when decrypting pgp/mime the
+        # Date-header is not copied.
+        new.date ||= self.date
         # Test if there's a signed multipart inside the ciphertext
         # ("encapsulated" format of pgp/mime).
         if new.signed?
@@ -23,6 +26,7 @@ module Mail
       end
 
       new.list = list
+      new.gpg list.gpg_sign_options
       new.original_message = self.dup.freeze
       new.recipient = recipient
       new
@@ -30,6 +34,8 @@ module Mail
 
     def clean_copy(with_pseudoheaders=false)
       clean = Mail.new
+      clean.list = self.list
+      clean.gpg self.list.gpg_sign_options
       clean.from = list.email
       clean.subject = self.subject
 
@@ -63,6 +69,19 @@ module Mail
       self.parts.unshift(parts.delete_at(parts.size-1))
     end
 
+    def add_footer!
+      # Add public_footer unless it's empty?.
+      if self.list.present? && ! self.list.public_footer.to_s.strip.empty?
+        footer_part = Mail::Part.new
+        footer_part.body = list.public_footer.strip
+        if parts.size == 1 && parts.first.mime_type == 'multipart/mixed' && parts.first.parts.size == 1 && parts.first.parts.first.mime_type == 'text/plain'
+          self.parts.first.add_part footer_part
+        else
+          self.add_part footer_part
+        end
+      end
+    end
+
     def was_encrypted?
       Mail::Gpg.encrypted?(original_message)
     end
@@ -147,14 +166,16 @@ module Mail
       @keywords
     end
 
-    def add_subject_prefix(string)
-      if ! string.to_s.strip.empty?
-        prefix = "#{string} "
-        # Only insert prefix if it's not present already.
-        if ! self.subject.include?(prefix)
-          self.subject = "#{string} #{self.subject}"
-        end
-      end
+    def add_subject_prefix!
+      _add_subject_prefix(nil)
+    end
+
+    def add_subject_prefix_in!
+      _add_subject_prefix(:in)
+    end
+
+    def add_subject_prefix_out!
+      _add_subject_prefix(:out)
     end
 
     def add_pseudoheader(key, value)
@@ -163,7 +184,7 @@ module Mail
     end
 
     def make_pseudoheader(key, value)
-      "#{key.to_s.capitalize}: #{value.to_s}"
+      "#{key.to_s.camelize}: #{value.to_s}"
     end
 
     def dynamic_pseudoheaders
@@ -184,16 +205,22 @@ module Mail
       # Careful to add information about the incoming signature. GPGME
       # throws exceptions if it doesn't know the key.
       if self.signature.present?
-        msg = begin
-                self.signature.to_s
-              rescue EOFError
-                "Unknown signature by 0x#{self.signature.fingerprint}"
-              end
+        # Some versions of gpgme return nil if the key is unknown, so we check
+        # for that manually and provide our own fallback. (Calling
+        # `signature.key` results in an EOFError in that case.)
+        if list.key(signature.fingerprint)
+          msg = signature.to_s
+        else
+          # TODO: I18n
+          msg = "Unknown signature by unknown key 0x#{self.signature.fingerprint}"
+        end
       else
+        # TODO: I18n
         msg = "Unsigned"
       end
       @standard_pseudoheaders << make_pseudoheader(:sig, msg)
 
+      # TODO: I18n
       @standard_pseudoheaders << make_pseudoheader(
             :enc,
             was_encrypted? ? 'Encrypted' : 'Unencrypted'
@@ -293,6 +320,25 @@ module Mail
     private
 
 
+    def _add_subject_prefix(suffix)
+      attrib = "subject_prefix"
+      if suffix
+        attrib << "_#{suffix}"
+      end
+      if ! self.list.respond_to?(attrib)
+        return false
+      end
+
+      string = self.list.send(attrib).to_s.strip
+      if ! string.empty?
+        prefix = "#{string} "
+        # Only insert prefix if it's not present already.
+        if ! self.subject.include?(prefix)
+          self.subject = "#{string} #{self.subject}"
+        end
+      end
+    end
+
     # Looking for signatures in each part. They are not aggregated into the main part.
     # We only return the signature if all parts are validly signed by the same key.
     def signature_multipart_inline
diff --git a/lib/schleuder/plugins/foo.rb b/lib/schleuder/plugins/foo.rb
deleted file mode 100644
index 6e1a8a5..0000000
--- a/lib/schleuder/plugins/foo.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module Schleuder
-  module ListPlugins
-    def self.foo(arguments, mail)
-      mail.add_pseudoheader :foo, 'Bar!'
-      nil
-    end
-  end
-end
diff --git a/lib/schleuder/plugins/resend.rb b/lib/schleuder/plugins/resend.rb
index 1897138..cc2815d 100644
--- a/lib/schleuder/plugins/resend.rb
+++ b/lib/schleuder/plugins/resend.rb
@@ -1,73 +1,158 @@
 module Schleuder
   module ListPlugins
     def self.resend(arguments, list, mail)
-      resend_it(arguments, list, mail, false)
-      # Return nil to prevent any erronous output to be interpreted as error.
-      nil
+      resend_it(arguments, mail, false)
+    end
+
+    def self.resend_enc(arguments, list, mail)
+      resend_encrypted_only(arguments, list, mail)
     end
 
     def self.resend_encrypted_only(arguments, list, mail)
-      resend_it(arguments, list, mail, true)
+      resend_it(arguments, mail, true)
+    end
+
+    def self.resend_cc(arguments, list, mail)
+      resend_it_cc(arguments, mail, false)
+    end
+
+    def self.resend_cc_enc(arguments, list, mail)
+      resend_cc_encrypted_only(arguments, list, mail)
+    end
+
+    def self.resend_cc_encrypted_only(arguments, list, mail)
+      resend_it_cc(arguments, mail, true)
+    end
+
+    def self.resend_it_cc(arguments, mail, encrypted_only)
+      recip_map = map_with_keys(mail, arguments, encrypted_only)
+
+      # Only continue if all recipients are still here.
+      if recip_map.size < arguments.size
+        return
+      end
+
+      if do_resend(mail, recip_map, :cc, encrypted_only)
+        mail.add_subject_prefix_out!
+      end
+
+      # Return nil to prevent any accidental output to be interpreted as an
+      # error.
       nil
     end
 
-    def self.resend_it(arguments, list, mail, send_encrypted_only)
-      # If we must encrypt, first test if there's a key for every recipient.
-      found_keys = {}
-      arguments.each do |email|
-        keys = list.keys_by_email(email)
+    def self.resend_it(arguments, mail, encrypted_only)
+      recip_map = map_with_keys(mail, arguments, encrypted_only)
+
+      resent_stati = recip_map.map do |email, key|
+        do_resend(mail, {email => key}, :to, encrypted_only)
+      end
+
+      if resent_stati.include?(true)
+        # At least one message has been resent
+        mail.add_subject_prefix_out!
+      end
+
+      # Return nil to prevent any accidental output to be interpreted as an
+      # error.
+      nil
+    end
+
+    def self.do_resend(mail, recipients_map, to_or_cc, encrypted_only)
+      if recipients_map.empty?
+        return
+      end
+
+      gpg_opts = make_gpg_opts(mail, recipients_map, encrypted_only)
+      if gpg_opts == false
+        return false
+      end
+
+      # Compose and send email
+      new = mail.clean_copy
+      new[to_or_cc] = recipients_map.keys
+      new.add_footer!
+      # `dup` gpg_opts because `deliver` changes their value and we need them
+      # below to determine encryption!
+      new.gpg gpg_opts.dup
+
+      if new.deliver
+        add_resent_headers(mail, recipients_map, to_or_cc, gpg_opts[:encrypt])
+        return true
+      else
+        add_error_header(mail, recipients_map)
+        return false
+      end
+    rescue Net::SMTPFatalError => exc
+      add_error_header(mail, recipients_map)
+      logger.error "Error while sending: #{exc}"
+      return false
+    end
+
+    def self.map_with_keys(mail, recipients, encrypted_only)
+      Array(recipients).inject({}) do |hash, email|
+        keys = mail.list.keys_by_email(email)
         case keys.size
-        when 0
-          # TODO: I18n.
-          mail.add_pseudoheader(:note, "No key found for #{email}.")
         when 1
-          found_keys[email] = keys.first
+          hash[email] = 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)
+          else
+            hash[email] = ''
+          end
         else
-          # TODO: I18n.
-          mail.add_pseudoheader(:note, "Multiple keys found for #{email}, not using any.")
+          # Always report this situation, regardless of sending or not. It's
+          # bad and should be fixed.
+          add_keys_error(mail, email, keys.size)
+          if ! encrypted_only
+            hash[email] = ''
+          end
         end
+        hash
       end
+    end
 
-      if send_encrypted_only
-        missing = arguments - found_keys.keys
-        if missing.present?
-          return I18n.t("plugins.resend.not_resent_no_keys", emails: missing.join(', '))
-        end
+    def self.make_gpg_opts(mail, recipients_map, encrypted_only)
+      gpg_opts = mail.list.gpg_sign_options
+      # Do all recipients have a key?
+      if recipients_map.values.map(&:class).uniq == [GPGME::Key]
+        gpg_opts.merge!(encrypt: true)
+      elsif encrypted_only
+        false
       end
+      gpg_opts
+    end
 
-      arguments.map do |email|
-        # Setup encryption
-        gpg_opts = {sign: true}
-        if found_keys[email].present?
-          gpg_opts.merge!(encrypt: true)
-        end
-
-        # Compose and send email
-        new = mail.clean_copy
-        new.to = email
+    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))
+    end
 
-        # Add public_footer unless it's empty?.
-        if ! list.public_footer.to_s.strip.empty?
-          footer_part = Mail::Part.new
-          footer_part.body = list.public_footer.strip
-          new.add_part footer_part
-        end
+    def self.add_error_header(mail, recipients_map)
+      mail.add_pseudoheader(:error, "Resending to #{recipients_map.keys.join(', ')} failed, please check the logs!")
+    end
 
-        new.gpg gpg_opts
-        if new.deliver
-          mail.add_pseudoheader('resent-to', resent_pseudoheader(email, found_keys[email]))
-          mail.add_subject_prefix(list.subject_prefix_out)
-        end
+    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|
+          "#{email} (#{key.fingerprint})"
+        end.join(', ')
+      else
+        prefix = I18n.t('plugins.resend.unencrypted_to')
+        str = recipients_map.keys.join(', ')
       end
-      # TODO: catch and handle SMTPFatalError (is raised when recipient is rejected by remote)
+      headername = resent_header_name(to_or_cc)
+      mail.add_pseudoheader(headername, "#{prefix} #{str}")
     end
 
-    def self.resent_pseudoheader(email, key)
-      str = email
-      if key.present?
-        str << " (#{I18n.t('plugins.resend.encrypted_with')} #{key.fingerprint})"
+    def self.resent_header_name(to_or_cc)
+      if to_or_cc.to_s == 'to'
+        'resent'
       else
-        str << " (#{I18n.t('plugins.resend.unencrypted')})"
+        'resent_cc'
       end
     end
   end
diff --git a/lib/schleuder/plugins/sign_this.rb b/lib/schleuder/plugins/sign_this.rb
index a51a81a..bfb6957 100644
--- a/lib/schleuder/plugins/sign_this.rb
+++ b/lib/schleuder/plugins/sign_this.rb
@@ -8,7 +8,8 @@ module Schleuder
         clearsign(mail)
       else
         # Here we need to send our reply manually because we're sending
-        # attachments. Maybe move this ability into the plugin-runner?
+        # attachments.
+        # TODO: Maybe move this ability into the plugin-runner?
         out = multipart(mail.reply, list, mail)
         out.body = I18n.t('plugins.signatures_attached')
         list.logger.info "Replying directly to sender"
@@ -34,15 +35,12 @@ module Schleuder
       out
     end
 
-    def self.sign_each_part(list, mail)
-    end
-
     def self.detachsign(thing)
       crypto.sign(thing, mode: GPGME::SIG_MODE_DETACH).to_s
     end
 
     def self.clearsign(mail)
-      return crypto.clearsign(mail.body.to_s).to_s
+      crypto.clearsign(mail.body.to_s).to_s
     end
 
     def self.crypto
diff --git a/lib/schleuder/runner.rb b/lib/schleuder/runner.rb
index bf216a3..a4fae83 100644
--- a/lib/schleuder/runner.rb
+++ b/lib/schleuder/runner.rb
@@ -25,23 +25,22 @@ module Schleuder
         return error
       end
 
+      if ! @mail.was_validly_signed?
+        logger.debug "Message was not validly signed, adding subject_prefix_in"
+        @mail.add_subject_prefix_in!
+      end
+
       if ! @mail.was_encrypted?
         logger.debug "Message was not encrypted, skipping plugins"
-      else
-        logger.debug "Message was encrypted."
-        if ! @mail.was_validly_signed?
-          logger.debug "Message was not validly signed, adding subject_prefix_in and skipping plugins"
-          @mail.add_subject_prefix(list.subject_prefix_in)
-        else
-          # Plugins
-          logger.debug "Message was encrypted and validly signed"
-          output = Plugins::Runner.run(list, @mail).compact
-
-          # Any output will be treated as error-message. Text meant for users
-          # should have been put into the mail by the plugin.
-          output.each do |something|
-            @mail.add_pseudoheader(:error, something.to_s) if something.present?
-          end
+      elsif @mail.was_validly_signed?
+        # Plugins
+        logger.debug "Message was encrypted and validly signed"
+        output = Plugins::Runner.run(list, @mail).compact
+
+        # Any output will be treated as error-message. Text meant for users
+        # should have been put into the mail by the plugin.
+        output.each do |something|
+          @mail.add_pseudoheader(:error, something.to_s) if something.present?
         end
       end
 
@@ -52,7 +51,7 @@ module Schleuder
       end
 
       logger.debug "Adding subject_prefix"
-      @mail.add_subject_prefix(list.subject_prefix)
+      @mail.add_subject_prefix!
 
       # Subscriptions
       send_to_subscriptions
@@ -103,14 +102,20 @@ module Schleuder
       end
 
       # Check neccessary permissions of crucial files.
-      if ! File.readable?(@list.listdir)
-        return log_and_return(Errors::ListdirProblem.new(@list.listdir, :not_readable))
+      if ! File.exist?(@list.listdir)
+        return log_and_return(Errors::ListdirProblem.new(@list.listdir, :not_existing))
       elsif ! File.directory?(@list.listdir)
         return log_and_return(Errors::ListdirProblem.new(@list.listdir, :not_a_directory))
+      elsif ! File.readable?(@list.listdir)
+        return log_and_return(Errors::ListdirProblem.new(@list.listdir, :not_readable))
+      elsif ! File.writable?(@list.listdir)
+        return log_and_return(Errors::ListdirProblem.new(@list.listdir, :not_writable))
+      else
+        if File.exist?(@list.logfile) && ! File.writable?(@list.logfile)
+          return log_and_return(Errors::ListdirProblem.new(@list.logfile, :not_writable))
+        end
       end
-      if ! File.writable?(@list.logfile)
-        return log_and_return(Errors::ListdirProblem.new(@list.logfile, :not_writable))
-      end
+
 
       # Set locale
       if I18n.available_locales.include?(@list.language.to_sym)
diff --git a/lib/schleuder/subscription.rb b/lib/schleuder/subscription.rb
index dd1aeb6..306f8b0 100644
--- a/lib/schleuder/subscription.rb
+++ b/lib/schleuder/subscription.rb
@@ -41,7 +41,7 @@ module Schleuder
       end
 
       mail = ensure_headers(mail)
-      gpg_opts = {encrypt: true, sign: true, keys: {self.email => "0x#{self.fingerprint}"}}
+      gpg_opts = self.list.gpg_sign_options.merge(encrypt: true, keys: {self.email => "0x#{self.fingerprint}"})
       if self.key.blank?
         if self.list.send_encrypted_only?
           self.list.logger.warn "Not sending to #{self.email}: no key present and sending plain text not allowed"
@@ -67,7 +67,7 @@ module Schleuder
       mail = ensure_headers(Mail.new)
       mail.subject = I18n.t('notice')
       mail.body = I18n.t("missed_message_due_to_absent_key", list_email: self.list.email) + I18n.t('errors.signoff')
-      mail.gpg({encrypt: false, sign: true})
+      mail.gpg self.list.gpg_sign_options
       mail.deliver
     end
 
diff --git a/lib/schleuder/version.rb b/lib/schleuder/version.rb
index 42395d1..34a4076 100644
--- a/lib/schleuder/version.rb
+++ b/lib/schleuder/version.rb
@@ -1,3 +1,3 @@
 module Schleuder
-  VERSION = '3.0.0.beta11'
+  VERSION = '3.0.0.beta12'
 end
diff --git a/locales/de.yml b/locales/de.yml
index b559313..6788e43 100644
--- a/locales/de.yml
+++ b/locales/de.yml
@@ -54,6 +54,7 @@ de:
     list_exists: Es existiert bereits eine Liste mit der Adresse '%{email}'.
     listdir_problem:
       message: "Problem mit dem Listen-Verzeichnis: '%{dir}' %{problem}."
+      not_existing: existiert nicht
       not_a_directory: ist kein Verzeichnis
       not_empty: ist nicht leer
       not_writable: ist nicht beschreibbar
@@ -80,9 +81,9 @@ de:
         updated: aktualisiert
         unchanged: unverändert
     resend:
-      not_resent_no_keys: "Das Versenden schlug fehl. Für die folgenden Adressen wurde kein Schlüssel gefunden und unverschlüsseltes Senden war untersagt: %{emails}."
-      encrypted_with: verschlüsselt mit
-      unencrypted: unverschlüsselt
+      not_resent_no_keys: Resending an <%{email}> fehlgeschlagen (%{num_keys} Schlüssel gefunden und unverschlüsseltes Senden verboten).
+      encrypted_to: Verschlüsselt an
+      unencrypted_to: Unverschlüsselt an
     subscription_management:
       forbidden: "Fehler: Du bist nicht berechtigt, das Abo für %{email} zu löschen."
       is_not_subscribed: Kein Abo für %{email} gefunden.
diff --git a/locales/en.yml b/locales/en.yml
index 944997c..53b2d8c 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -54,6 +54,7 @@ en:
     list_exists: A list with address '%{email}' is already present.
     listdir_problem:
       message: "There's a problem with the list-directory: '%{dir}' %{problem}."
+      not_existing: does not exist
       not_a_directory: is not a directory
       not_empty: is not empty
       not_writable: is not writable
@@ -80,9 +81,9 @@ en:
         updated: updated
         unchanged: unchanged
     resend:
-      not_resent_no_keys: "Resending failed. For the following recipients no matching key was found and unencrypted sending was disallowed: %{emails}."
-      encrypted_with: encrypted with
-      unencrypted: unencrypted
+      not_resent_no_keys: Resending to <%{email}> failed (%{num_keys} keys found and unencrypted sending disallowed).
+      encrypted_to: Encrypted to
+      unencrypted_to: Unencrypted to
     subscription_management:
       forbidden: "Error: You're not allowed to unsubscribe %{email}."
       is_not_subscribed: "%{email} is not subscribed."
diff --git a/schleuder.gemspec b/schleuder.gemspec
index a7fcf1b..ec1b7d8 100644
--- a/schleuder.gemspec
+++ b/schleuder.gemspec
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
   s.add_runtime_dependency 'thin', '~> 1'
   s.add_development_dependency 'rspec', '~> 3.5.0'
   s.add_development_dependency 'hirb'
+  s.add_development_dependency 'factory_girl'
   s.add_development_dependency 'database_cleaner'
   s.post_install_message = "
 
diff --git a/spec/factories/lists.rb b/spec/factories/lists.rb
new file mode 100644
index 0000000..0c26d5f
--- /dev/null
+++ b/spec/factories/lists.rb
@@ -0,0 +1,39 @@
+FactoryGirl.define do
+  factory :list do
+    sequence(:email) {|n| "list#{n}@example.org" }
+    fingerprint "59C71FB38AEE22E091C78259D06350440F759BD3"
+    log_level "warn"
+    subject_prefix nil
+    subject_prefix_in nil
+    subject_prefix_out nil
+    openpgp_header_preference "signencrypt"
+    public_footer nil
+    headers_to_meta ["from", "to", "cc", "date"]
+    bounces_drop_on_headers "x-spam-flag" => true
+    keywords_admin_only ["subscribe", "unsubscribe", "delete-key"]
+    keywords_admin_notify ["add-key"]
+    send_encrypted_only true
+    receive_encrypted_only false
+    receive_signed_only false
+    receive_authenticated_only false
+    receive_from_subscribed_emailaddresses_only false
+    receive_admin_only false
+    keep_msgid true
+    bounces_drop_all false
+    bounces_notify_admins true
+    include_list_headers true
+    include_openpgp_header true
+    max_message_size_kb 10240
+    language "en"
+    forward_all_incoming_to_admins false
+    logfiles_to_keep 2
+
+    trait :with_one_subscription do
+      after(:build) do |list|
+        create(:subscription)
+      end
+    end
+
+    factory :list_with_one_subscription, traits: [:with_one_subscription]
+  end
+end
diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb
new file mode 100644
index 0000000..475f64d
--- /dev/null
+++ b/spec/factories/subscriptions.rb
@@ -0,0 +1,9 @@
+FactoryGirl.define do
+  factory :subscription do
+    list
+    sequence(:email) {|n| "subscription#{n}@example.org" }
+    fingerprint "129A74AD5317457F9E502844A39C61B32003A8D8"
+    admin true
+    delivery_enabled true
+  end
+end
diff --git a/spec/fixtures/example_key.txt b/spec/fixtures/example_key.txt
new file mode 100644
index 0000000..373a870
--- /dev/null
+++ b/spec/fixtures/example_key.txt
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG/MacGPG2 v2
+
+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
+gpCMrbUUdX4=
+=H2Gw
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/spec/gnupg/pubring.gpg~ b/spec/gnupg/pubring.gpg~
deleted file mode 100644
index 7738906..0000000
Binary files a/spec/gnupg/pubring.gpg~ and /dev/null differ
diff --git a/spec/schleuder/list_spec.rb b/spec/schleuder/list_spec.rb
index 6a83e0b..0610904 100644
--- a/spec/schleuder/list_spec.rb
+++ b/spec/schleuder/list_spec.rb
@@ -10,6 +10,12 @@ describe Schleuder::List do
       :include_openpgp_header, :forward_all_incoming_to_admins
   ].freeze
 
+  it "has a valid factory" do
+    list = create(:list_with_one_subscription)
+
+    expect(list).to be_valid
+  end
+
   it { is_expected.to respond_to :subscriptions }
   it { is_expected.to respond_to :email }
   it { is_expected.to respond_to :fingerprint }
@@ -40,30 +46,21 @@ describe Schleuder::List do
   it { is_expected.to respond_to :logfiles_to_keep }
 
   it "is invalid when email is nil" do
-    list = Schleuder::List.new(
-      email: nil,
-      fingerprint: "aaaadddd0000999",
-    )
+    list = build(:list, email: nil)
 
     expect(list).not_to be_valid
     expect(list.errors.messages[:email]).to include("can't be blank")
   end
 
-   it "is invalid when email is blank" do
-    list = Schleuder::List.new(
-      email: "",
-      fingerprint: "aaaadddd0000999",
-    )
+  it "is invalid when email is blank" do
+    list = build(:list, email: "")
 
     expect(list).not_to be_valid
     expect(list.errors.messages[:email]).to include("can't be blank")
   end
 
   it "is invalid when email does not contain an @" do
-    list = Schleuder::List.new(
-      email: "fooatbar.org",
-      fingerprint: "aaaadddd0000999",
-    )
+    list = build(:list, email: "fooatbar.org")
 
     expect(list).not_to be_valid
     expect(list.errors.messages[:email]).to include("is not a valid email address")
@@ -71,36 +68,27 @@ describe Schleuder::List do
 
   it "normalizes the fingerprint" do
     fingerprint = " 99 991 1000 10"
-    list = Schleuder::List.new(fingerprint: fingerprint)
+    list = build(:list, fingerprint: fingerprint)
 
     expect(list.fingerprint).to eq "99991100010"
   end
 
   it "is invalid when fingerprint is blank" do
-    list = Schleuder::List.new(
-      email: "foo at bar.org",
-      fingerprint: "",
-    )
+    list = build(:list, fingerprint: "")
 
     expect(list).not_to be_valid
     expect(list.errors.messages[:fingerprint]).to include("can't be blank")
   end
 
   it "is invalid when fingerprint is nil" do
-    list = Schleuder::List.new(
-      email: "foo at bar.org",
-      fingerprint: nil
-    )
+    list = build(:list, fingerprint: nil)
 
     expect(list).not_to be_valid
     expect(list.errors.messages[:fingerprint]).to include("can't be blank")
   end
 
   it "is invalid when fingerprint contains invalid characters" do
-    list = Schleuder::List.new(
-      email: "foo at bar.org",
-      fingerprint: "&$$$$67923AAA",
-    )
+    list = build(:list, fingerprint: "&$$$$67923AAA")
 
     expect(list).not_to be_valid
     expect(list.errors.messages[:fingerprint]).to include("is not a valid fingerprint")
@@ -108,10 +96,7 @@ describe Schleuder::List do
 
   BOOLEAN_LIST_ATTRIBUTES.each do |list_attribute|
     it "is invalid if #{list_attribute} is nil" do
-      list = Schleuder::List.new(
-        email: "foo at bar.org",
-        fingerprint: "aaaadddd0000999",
-      )
+      list = build(:list)
       list[list_attribute] = nil
 
       expect(list).not_to be_valid
@@ -119,10 +104,7 @@ describe Schleuder::List do
     end
 
     it "is invalid if #{list_attribute} is blank" do
-      list = Schleuder::List.new(
-        email: "foo at bar.org",
-        fingerprint: "aaaadddd0000999",
-      )
+      list = build(:list)
       list[list_attribute] = ""
 
       expect(list).not_to be_valid
@@ -132,10 +114,7 @@ describe Schleuder::List do
 
   [:headers_to_meta, :keywords_admin_only, :keywords_admin_notify].each do |list_attribute|
     it "is invalid if #{list_attribute} contains special characters" do
-      list = Schleuder::List.new(
-        email: "foo at bar.org",
-        fingerprint: "aaaadddd0000999",
-      )
+      list = build(:list)
       list[list_attribute] =["$from", "to", "date", "cc"]
 
       expect(list).not_to be_valid
@@ -143,10 +122,7 @@ describe Schleuder::List do
     end
 
     it "is valid if #{list_attribute} does not contain special characters" do
-      list = Schleuder::List.new(
-        email: "foo at bar.org",
-        fingerprint: "aaaadddd0000999",
-      )
+      list = build(:list)
       list[list_attribute] = ["foobar"]
 
       expect(list).to be_valid
@@ -154,11 +130,7 @@ describe Schleuder::List do
   end
 
   it "is invalid if bounces_drop_on_headers contains special characters" do
-    list = Schleuder::List.new(
-      email: "foo at bar.org",
-      fingerprint: "aaaadddd0000999",
-      bounces_drop_on_headers: {"$" => "%"},
-    )
+    list = build(:list, bounces_drop_on_headers: {"$" => "%"})
 
     expect(list).not_to be_valid
     expect(list.errors.messages[:bounces_drop_on_headers]).to include("contains invalid characters")
@@ -166,10 +138,7 @@ describe Schleuder::List do
 
   [:subject_prefix, :subject_prefix_in, :subject_prefix_out].each do |list_attribute|
     it "is invalid if #{list_attribute} contains a linebreak" do
-      list = Schleuder::List.new(
-        email: "foo at bar.org",
-        fingerprint: "aaaadddd0000999",
-      )
+      list = build(:list)
       list[list_attribute] = "Foo\nbar"
 
       expect(list).not_to be_valid
@@ -177,10 +146,7 @@ describe Schleuder::List do
     end
 
     it "is valid if #{list_attribute} is nil" do
-      list = Schleuder::List.new(
-        email: "foo at bar.org",
-        fingerprint: "aaaadddd0000999",
-      )
+      list = build(:list)
       list[list_attribute] = nil
 
       expect(list).to be_valid
@@ -188,11 +154,7 @@ describe Schleuder::List do
   end
 
   it "is invalid if openpgp_header_preference is foobar" do
-    list = Schleuder::List.new(
-      email: "foo at bar.org",
-      fingerprint: "aaaadddd0000999",
-      openpgp_header_preference: "foobar",
-    )
+    list = build(:list, openpgp_header_preference: "foobar")
 
     expect(list).not_to be_valid
     expect(list.errors.messages[:openpgp_header_preference]).to include("must be one of: sign, encrypt, signencrypt, unprotected, none")
@@ -200,10 +162,7 @@ describe Schleuder::List do
 
   [:max_message_size_kb, :logfiles_to_keep].each do |list_attribute|
     it "is invalid if #{list_attribute} is 0" do
-      list = Schleuder::List.new(
-        email: "foo at bar.org",
-        fingerprint: "aaaadddd0000999",
-      )
+      list = build(:list)
       list[list_attribute] = 0
 
       expect(list).not_to be_valid
@@ -212,33 +171,21 @@ describe Schleuder::List do
   end
 
   it "is invalid if log_level is foobar" do
-    list = Schleuder::List.new(
-      email: "foo at bar.org",
-      fingerprint: "aaaadddd0000999",
-      log_level: "foobar",
-    )
+    list = build(:list, log_level: "foobar")
 
     expect(list).not_to be_valid
     expect(list.errors.messages[:log_level]).to include("must be one of: debug, info, warn, error")
   end
 
   it "is invalid if language is jp" do
-    list = Schleuder::List.new(
-      email: "foo at bar.org",
-      fingerprint: "aaaadddd0000999",
-      language: "jp",
-    )
+    list = build(:list, language: "jp")
 
     expect(list).not_to be_valid
     expect(list.errors.messages[:language]).to include("must be one of: en, de")
   end
 
   it "is invalid if public footer include a non-printable characters" do
-    list = Schleuder::List.new(
-      email: "foo at bar.org",
-      fingerprint: "aaaadddd0000999",
-      public_footer: "\a",
-    )
+    list = build(:list, public_footer: "\a")
 
     expect(list).not_to be_valid
     expect(list.errors.messages[:public_footer]).to include("includes non-printable characters")
@@ -265,21 +212,15 @@ describe Schleuder::List do
 
   describe "#logfile" do
     it "returns the logfile path" do
-      list = Schleuder::List.new(
-        email: "foo at bar.org",
-        fingerprint: "aaaadddd0000999",
-      )
+      list = create(:list, email: "foo at bar.org")
 
-      expect(list.logfile).to eq "/var/schleuder/lists/bar.org/foo/list.log"
+      expect(list.logfile).to eq File.join(Schleuder::Conf.listlogs_dir, "bar.org/foo/list.log")
     end
   end
 
   describe "#logger" do
     it "calls the ListLogger" do
-      list = Schleuder::List.new(
-        email: "foo at bar.org",
-        fingerprint: "aaaadddd0000999",
-      )
+      list = create(:list)
 
       expect(Listlogger).to receive(:new).with(list)
 
@@ -289,10 +230,7 @@ describe Schleuder::List do
 
   describe "#to_s" do
     it "returns the email" do
-      list = Schleuder::List.new(
-        email: "foo at bar.org",
-        fingerprint: "aaaadddd0000999",
-      )
+      list = create(:list, email: "foo at bar.org")
 
       expect(list.email).to eq "foo at bar.org"
     end
@@ -321,7 +259,6 @@ describe Schleuder::List do
 
   describe "#key" do
     it "returns the key with the fingerprint of the list" do
-      set_test_gnupg_home
       list = Schleuder::List.create(
         email: "foo at bar.org",
         fingerprint: "59C7 1FB3 8AEE 22E0 91C7  8259 D063 5044 0F75 9BD3",
@@ -330,4 +267,78 @@ describe Schleuder::List do
       expect(list.key.fingerprint()).to eq "59C71FB38AEE22E091C78259D06350440F759BD3"
     end
   end
+
+  describe "#secret_key" do
+    it "returns the secret key with the fingerprint of the list" do
+      list = create(
+        :list,
+        fingerprint: "59C71FB38AEE22E091C78259D06350440F759BD3"
+      )
+
+      expect(list.secret_key.secret?).to eq true
+      expect(list.secret_key.fingerprint).to eq "59C71FB38AEE22E091C78259D06350440F759BD3"
+    end
+  end
+
+  describe "#keys" do
+    it "it returns an array with the keys of the list" do
+      list = create(:list)
+
+      expect(list.keys).to be_kind_of Array
+      expect(list.keys.length).to eq 1
+    end
+
+    it "returns an array of keys matching the given fingerprint" do
+      list = create(
+        :list,
+        fingerprint: "59C71FB38AEE22E091C78259D06350440F759BD3"
+      )
+
+      expect(list.keys).to be_kind_of Array
+      expect(list.keys.first.fingerprint).to eq "59C71FB38AEE22E091C78259D06350440F759BD3"
+    end
+  end
+
+  describe "#keys_by_email" do
+    it "returns an array with the keys matching the given email address" do
+      list = create(:list, email: "schleuder at example.org")
+
+      expect(list.keys_by_email("schleuder at example.org").length).to eq 1
+      expect(
+        list.keys_by_email("schleuder at example.org").first.fingerprint
+      ).to eq "59C71FB38AEE22E091C78259D06350440F759BD3"
+    end
+  end
+
+  describe "#import_key" do
+    it "imports a given key" do
+      set_test_gnupg_home
+      list = create(:list)
+      key = File.read("spec/fixtures/example_key.txt")
+
+      expect { list.import_key(key) }.to change { list.keys.count }.by(1)
+
+      list.delete_key("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE")
+    end
+  end
+
+  describe "#delete_key" do
+    it "deletes the key with the given fingerprint" do
+      set_test_gnupg_home
+      list = create(:list)
+      key = File.read("spec/fixtures/example_key.txt")
+      list.import_key(key)
+
+      expect do
+        list.delete_key("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE")
+      end.to change { list.keys.count }.by(-1)
+    end
+
+    it "returns false if no key with the fingerprint was found" do
+      set_test_gnupg_home
+      list = create(:list)
+
+      expect(list.delete_key("A4C60C8833789C7CAA44496FD3FFA6611AB10CEC")).to eq false
+    end
+  end
 end
diff --git a/spec/schleuder/subscription_spec.rb b/spec/schleuder/subscription_spec.rb
index f5d2f10..72ee3c8 100644
--- a/spec/schleuder/subscription_spec.rb
+++ b/spec/schleuder/subscription_spec.rb
@@ -7,8 +7,10 @@ describe Schleuder::Subscription do
       :admin
   ].freeze
 
-  before(:example) do
-    @list = Schleuder::List.first || Schleuder::List.create(email: 'test at whatever', fingerprint: 'aaaadddd0000999')
+  it "has a valid factory" do
+    subscription = create(:subscription)
+
+    expect(subscription).to be_valid
   end
 
   it { is_expected.to respond_to :list_id }
@@ -18,43 +20,28 @@ describe Schleuder::Subscription do
   it { is_expected.to respond_to :delivery_enabled }
 
   it "is invalid when list_id is blank" do
-    subscription = Schleuder::Subscription.new(
-      email: "foo at bar.org",
-      fingerprint: "aaaadddd0000999",
-    )
+    subscription = build(:subscription, list_id: "")
 
     expect(subscription).not_to be_valid
     expect(subscription.errors.messages[:list_id]).to be_present
   end
 
   it "is invalid when email is nil" do
-    subscription = Schleuder::Subscription.new(
-      list_id: @list.id,
-      email: nil,
-      fingerprint: "aaaadddd0000999",
-    )
+    subscription = build(:subscription, email: nil)
 
     expect(subscription).not_to be_valid
     expect(subscription.errors.messages[:email]).to include("can't be blank")
   end
 
    it "is invalid when email is blank" do
-    subscription = Schleuder::Subscription.new(
-      list_id: @list.id,
-      email: "",
-      fingerprint: "aaaadddd0000999",
-    )
+    subscription = build(:subscription, email: "")
 
     expect(subscription).not_to be_valid
     expect(subscription.errors.messages[:email]).to include("can't be blank")
   end
 
   it "is invalid when email does not contain an @" do
-    subscription = Schleuder::Subscription.new(
-      list_id: @list.id,
-      email: "fooatbar.org",
-      fingerprint: "aaaadddd0000999",
-    )
+    subscription = build(:subscription, email: "fooatbar.org")
 
     expect(subscription).not_to be_valid
     expect(subscription.errors.messages[:email]).to include("is not a valid email address")
@@ -62,39 +49,27 @@ describe Schleuder::Subscription do
 
   it "normalizes the fingerprint" do
     fingerprint = " 99 991 1000 10"
-    subscription = Schleuder::Subscription.new(fingerprint: fingerprint)
+    subscription = build(:subscription, fingerprint: fingerprint)
 
     expect(subscription.fingerprint).to eq "99991100010"
   end
 
   it "is valid when fingerprint is empty" do
-    subscription = Schleuder::Subscription.new(
-      list_id: @list.id,
-      email: "foo at bar.org",
-      fingerprint: "",
-    )
+    subscription = build(:subscription, fingerprint: "")
 
     expect(subscription).to be_valid
     expect(subscription.errors.messages[:fingerprint]).to be_blank
   end
 
   it "is valid when fingerprint is nil" do
-    subscription = Schleuder::Subscription.new(
-      list_id: @list.id,
-      email: "foo at bar.org",
-      fingerprint: nil
-    )
+    subscription = build(:subscription, fingerprint: nil)
 
     expect(subscription).to be_valid
     expect(subscription.errors.messages[:fingerprint]).to be_blank
   end
 
   it "is invalid when fingerprint contains invalid characters" do
-    subscription = Schleuder::Subscription.new(
-      list_id: @list.id,
-      email: "foo at bar.org",
-      fingerprint: "&$$$$123AAA",
-    )
+    subscription = build(:subscription, fingerprint: "&$$$$123AAA")
 
     expect(subscription).not_to be_valid
     expect(subscription.errors.messages[:fingerprint]).to include("is not a valid fingerprint")
@@ -102,11 +77,7 @@ describe Schleuder::Subscription do
 
   BOOLEAN_SUBSCRIPTION_ATTRIBUTES.each do |subscription_attribute|
     it "is invalid if #{subscription_attribute} is nil" do
-      subscription = Schleuder::Subscription.new(
-        list_id: @list.id,
-        email: "foo at bar.org",
-        fingerprint: nil,
-      )
+      subscription = build(:subscription)
       subscription[subscription_attribute] = nil
 
       expect(subscription).not_to be_valid
@@ -114,11 +85,7 @@ describe Schleuder::Subscription do
     end
 
     it "is invalid if #{subscription_attribute} is blank" do
-      subscription = Schleuder::Subscription.new(
-        list_id: @list.id,
-        email: "foo at bar.org",
-        fingerprint: nil,
-      )
+      subscription = build(:subscription)
       subscription[subscription_attribute] = ""
 
       expect(subscription).not_to be_valid
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 74454a6..4e76173 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -4,6 +4,7 @@ require 'bundler/setup'
 Bundler.setup
 require 'schleuder'
 require 'database_cleaner'
+require 'factory_girl'
 
 RSpec.configure do |config|
   config.expect_with :rspec do |expectations|
@@ -19,6 +20,11 @@ RSpec.configure do |config|
 
   config.order = :random
 
+  config.include FactoryGirl::Syntax::Methods
+  config.before(:suite) do
+    FactoryGirl.find_definitions
+  end
+
   config.before(:suite) do
     DatabaseCleaner.strategy = :transaction
     DatabaseCleaner.clean_with(:truncation)
@@ -30,6 +36,18 @@ RSpec.configure do |config|
     end
   end
 
+  config.after(:each) do |example|
+    FileUtils.rm_rf(Dir["spec/gnupg/pubring.gpg~"])
+  end
+
+  config.before(:suite) do
+    set_test_gnupg_home
+  end
+
+  config.after(:suite) do
+    cleanup_gnupg_home
+  end
+
   # rspec-mocks config goes here. You can use an alternate test double
   # library (such as bogus or mocha) by changing the `mock_with` option here.
   config.mock_with :rspec do |mocks|
@@ -40,6 +58,16 @@ RSpec.configure do |config|
   end
 
   def set_test_gnupg_home
-    ENV["GNUPGHOME"] = "spec/gnupg"
+    gpghome_upstream = File.join File.dirname(__dir__), "spec", "gnupg"
+    gpghome_tmp = "/tmp/schleuder-#{Time.now.to_i}-#{rand(100)}"
+    Dir.mkdir(gpghome_tmp)
+    ENV["GNUPGHOME"] = gpghome_tmp
+    FileUtils.cp_r Dir["#{gpghome_upstream}/{private*,*.gpg,.*migrated}"], gpghome_tmp
+  end
+
+  def cleanup_gnupg_home
+    FileUtils.rm_rf(ENV["GNUPGHOME"])
+    ENV["GNUPGHOME"] = nil
+    puts `gpgconf --kill gpg-agent 2>&1`
   end
 end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/schleuder.git



More information about the Pkg-ruby-extras-commits mailing list