From c2737d4d972df30725e417bed0940fc8df8e88bd Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 4 Jan 2014 14:41:08 -0500 Subject: [PATCH 01/16] Factor out plugin running into the Plugin module (along with the includers() handling). --- bin/mailshears | 15 +---------- lib/common/plugin.rb | 57 +++++++++++++++++++++++++++------------ lib/mv/mv_plugin.rb | 16 +++-------- lib/prune/prune_plugin.rb | 20 +++----------- lib/rm/rm_plugin.rb | 18 ++++--------- 5 files changed, 53 insertions(+), 73 deletions(-) diff --git a/bin/mailshears b/bin/mailshears index 215bd6b..f7d5afa 100755 --- a/bin/mailshears +++ b/bin/mailshears @@ -101,20 +101,7 @@ require 'stringio' output_buffer = StringIO.new() $stdout = output_buffer -plugin_module.includers.each do |plugin_module_includer| - plugin = plugin_module_includer.new(cfg) - - if cfg.i_mean_business then - runner = plugin.runner().new() - else - runner = plugin.dummy_runner().new() - end - - # The splat passes the correct (we hope) number of arguments to the - # appropriate runner. The Rm(Dummy)Runner have splats on their - # *target arguments as well, to turn ARGV back into an array. - runner.run(plugin, *ARGV) -end +plugin_module.run(cfg, *ARGV) # Restore stdout, and print the header plus whatever the plugins # produced if they produced anything. If they didn't, we avoid diff --git a/lib/common/plugin.rb b/lib/common/plugin.rb index ed4ea00..81e1fbb 100644 --- a/lib/common/plugin.rb +++ b/lib/common/plugin.rb @@ -2,26 +2,49 @@ # operations that all plugins are supposed to support. module Plugin - def Plugin.included(c) - # Callback, called whenever another class or module includes this - # one. The parameter given is the name of the class or module - # that included us. - @includers ||= [] - @includers << c - end + module Run + # Module methods, meant to be extended. Includers can explicitly + # extend this to get a run() method defined in terms of their own + # runner() and dummy_runner() methods. - def Plugin.includers - return @includers - end + def included(c) + # Callback, called whenever another class or module includes this + # one. The parameter given is the name of the class or module + # that included us. + @includers ||= [] + @includers << c + end - def runner() - # The Runner associated with this plugin. - raise NotImplementedError - end + def includers + return @includers + end - def dummy_runner() - # The RummyRunner associated with this plugin. - raise NotImplementedError + def runner() + # The Runner associated with this plugin. + raise NotImplementedError + end + + def dummy_runner() + # The RummyRunner associated with this plugin. + raise NotImplementedError + end + + def run(cfg, *args) + includers().each do |includer| + plugin = includer.new(cfg) + + if cfg.i_mean_business then + runner = runner().new() + else + runner = dummy_runner().new() + end + + # The splat passes the correct (we hope) number of arguments to the + # appropriate runner. The Rm(Dummy)Runner have splats on their + # *target arguments as well, to turn ARGV back into an array. + runner.run(plugin, *args) + end + end end def describe_domain(domain) diff --git a/lib/mv/mv_plugin.rb b/lib/mv/mv_plugin.rb index 958f6d4..4b61603 100644 --- a/lib/mv/mv_plugin.rb +++ b/lib/mv/mv_plugin.rb @@ -3,23 +3,13 @@ module MvPlugin # Plugins for moving (renaming) users. # - def MvPlugin.included(c) - # Callback, called whenever another class or module includes this - # one. The parameter given is the name of the class or module - # that included us. - @includers ||= [] - @includers << c - end - - def MvPlugin.includers - return @includers - end + extend Plugin::Run - def runner() + def self.runner() return MvRunner end - def dummy_runner() + def self.dummy_runner() return MvDummyRunner end diff --git a/lib/prune/prune_plugin.rb b/lib/prune/prune_plugin.rb index 0fbe69d..44fd636 100644 --- a/lib/prune/prune_plugin.rb +++ b/lib/prune/prune_plugin.rb @@ -1,31 +1,19 @@ require 'rm/rm_plugin' module PrunePlugin - include RmPlugin - # # Plugins for the removal of leftover non-PostfixAdmin users, # i.e. after an user has been removed from the PostfixAdmin # database. # + include RmPlugin + extend Plugin::Run - def PrunePlugin.included(c) - # Callback, called whenever another class or module includes this - # one. The parameter given is the name of the class or module - # that included us. - @includers ||= [] - @includers << c - end - - def PrunePlugin.includers - return @includers - end - - def runner() + def self.runner() return PruneRunner end - def dummy_runner + def self.dummy_runner return PruneDummyRunner end diff --git a/lib/rm/rm_plugin.rb b/lib/rm/rm_plugin.rb index 95c595a..fe938b1 100644 --- a/lib/rm/rm_plugin.rb +++ b/lib/rm/rm_plugin.rb @@ -1,25 +1,17 @@ +require 'common/plugin.rb' + module RmPlugin # # Plugins for the removal of users. # - def RmPlugin.included(c) - # Callback, called whenever another class or module includes this - # one. The parameter given is the name of the class or module - # that included us. - @includers ||= [] - @includers << c - end - - def RmPlugin.includers - return @includers - end + extend Plugin::Run - def runner() + def self.runner() return RmRunner end - def dummy_runner() + def self.dummy_runner() return RmDummyRunner end -- 2.43.2 From c5a1271a37bed990f2fd8caa8fcde23a6e107b46 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 4 Jan 2014 14:57:01 -0500 Subject: [PATCH 02/16] Explicitly require 'common/errors' in RmRunner. --- lib/rm/rm_runner.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rm/rm_runner.rb b/lib/rm/rm_runner.rb index b953898..7142154 100644 --- a/lib/rm/rm_runner.rb +++ b/lib/rm/rm_runner.rb @@ -1,3 +1,4 @@ +require 'common/errors' require 'common/runner' class RmRunner -- 2.43.2 From 346e4e6a1b8031038e5be948dda0a19680b1bfb8 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 4 Jan 2014 14:57:23 -0500 Subject: [PATCH 03/16] Mean business when testing. --- test/mailshears.test.conf.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mailshears.test.conf.yml b/test/mailshears.test.conf.yml index d02d89e..662665a 100644 --- a/test/mailshears.test.conf.yml +++ b/test/mailshears.test.conf.yml @@ -1,4 +1,4 @@ -i_mean_business: false +i_mean_business: true postfixadmin_dbhost: localhost postfixadmin_dbport: 5432 -- 2.43.2 From 9a45da513ea3ceda803f841eca59ee1bd15b9e97 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 4 Jan 2014 15:00:13 -0500 Subject: [PATCH 04/16] Fix a crashy typo. --- lib/prune/prune_runner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prune/prune_runner.rb b/lib/prune/prune_runner.rb index 7ea6191..12a02d7 100644 --- a/lib/prune/prune_runner.rb +++ b/lib/prune/prune_runner.rb @@ -20,7 +20,7 @@ class PruneRunner leftover_domains = plugin.get_leftover_domains(db_domains) leftover_users.each do |user| - user_description = plugin.describe_users(user) + user_description = plugin.describe_user(user) plugin.delete_user(user) report(plugin, "Removed user: #{user} (#{user_description})") end -- 2.43.2 From 25ce6f72160050d0d13531abb0158286ab093d14 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 4 Jan 2014 15:01:12 -0500 Subject: [PATCH 05/16] Add a real rm test. --- test/rm_account_test.rb | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/test/rm_account_test.rb b/test/rm_account_test.rb index e39ead5..bd0ac30 100644 --- a/test/rm_account_test.rb +++ b/test/rm_account_test.rb @@ -1,5 +1,6 @@ require 'pg' require 'test/unit' + require 'common/configuration' class RmAccountTest < Test::Unit::TestCase @@ -27,8 +28,35 @@ class RmAccountTest < Test::Unit::TestCase end - def test_dummy - assert(true) + def test_single_rm + cfg = Configuration.new(TESTCONF_PATH) + argv = ["adam@example.net"] + + # Load each of the plugins that we'll need. + cfg.plugins.each do |plugin_file| + require "rm/plugins/#{plugin_file}" + end + + # And the runners. + require "rm/rm_runner" + require "rm/rm_dummy_runner" + + require 'stringio' + output_buffer = StringIO.new() + + $stdout = output_buffer + plugin_class = RmPlugin.run(cfg, *argv) + $stdout = STDOUT + + actual = output_buffer.string() + + expected = "PostfixadminRm - Removed user: " + + "adam@example.net (adam@example.net)\n" + + "RoundcubeRm - Removed user: adam@example.net (User ID: 2)\n" + + "AgendavRm - Removed user: adam@example.net " + + "(Username: adam@example.net)\n" + + "DavicalRm - User not found: adam@example.net\n" + assert(actual == expected) end def setup -- 2.43.2 From bf7d0402eda27d9487ca9402156818545fdda286 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 4 Jan 2014 22:35:46 -0500 Subject: [PATCH 06/16] Make list_users() methods public in several plugins. Add a list_aliases() method to the PostfixAdminPlugin module. Rename the existing test file. Add a README.fixtures giving an overview of what's in the test databases. Check the database for expected contents after removing a user and domain. --- Rakefile | 2 +- lib/common/agendav_plugin.rb | 7 +- lib/common/davical_plugin.rb | 46 ++++--- lib/common/postfixadmin_plugin.rb | 33 ++++- lib/common/roundcube_plugin.rb | 41 +++--- test/rm_account_test.rb | 112 ---------------- test/sql/README.fixtures | 113 ++++++++++++++++ test/test_rm.rb | 213 ++++++++++++++++++++++++++++++ 8 files changed, 408 insertions(+), 159 deletions(-) delete mode 100644 test/rm_account_test.rb create mode 100644 test/sql/README.fixtures create mode 100644 test/test_rm.rb diff --git a/Rakefile b/Rakefile index c859ae6..7138d8a 100644 --- a/Rakefile +++ b/Rakefile @@ -18,7 +18,7 @@ task :install => :build do end Rake::TestTask.new do |t| - t.pattern = 'test/*test.rb' + t.pattern = 'test/test*.rb' t.libs << 'test' end diff --git a/lib/common/agendav_plugin.rb b/lib/common/agendav_plugin.rb index 7522b2c..ae9b11c 100644 --- a/lib/common/agendav_plugin.rb +++ b/lib/common/agendav_plugin.rb @@ -32,10 +32,11 @@ module AgendavPlugin end - protected; - - def list_users() + # + # Produce a list of AgenDAV users. This is public because it's + # useful in testing. + # usernames = [] # Just assume PostgreSQL for now. diff --git a/lib/common/davical_plugin.rb b/lib/common/davical_plugin.rb index a1dcba0..8c9646f 100644 --- a/lib/common/davical_plugin.rb +++ b/lib/common/davical_plugin.rb @@ -33,10 +33,12 @@ module DavicalPlugin end - protected; - - def get_principal_id(user) - principal_id = nil + def list_users() + # + # Produce a list of DAViCal users. This is public because it's + # useful for testing. + # + usernames = [] begin connection = PGconn.connect(@db_host, @@ -47,30 +49,27 @@ module DavicalPlugin @db_user, @db_pass) - sql_query = "SELECT principal.principal_id " - sql_query += "FROM (principal INNER JOIN usr " - sql_query += " ON principal.user_no = usr.user_no) " - sql_query += "WHERE usr.username = $1;" + # User #1 is the super-user, and not tied to an email address. + sql_query = "SELECT username FROM usr WHERE user_no > 1" - connection.query(sql_query, [user]) do |result| - if result.num_tuples > 0 - principal_id = result[0]['principal_id'] - end + connection.query(sql_query) do |result| + usernames = result.field_values('username') end connection.close() - rescue PGError => e # Pretend like we're database-agnostic in case we ever are. raise DatabaseError.new(e) end - return principal_id + return usernames end - def list_users() - usernames = [] + protected; + + def get_principal_id(user) + principal_id = nil begin connection = PGconn.connect(@db_host, @@ -81,20 +80,25 @@ module DavicalPlugin @db_user, @db_pass) - # User #1 is the super-user, and not tied to an email address. - sql_query = "SELECT username FROM usr WHERE user_no > 1" + sql_query = "SELECT principal.principal_id " + sql_query += "FROM (principal INNER JOIN usr " + sql_query += " ON principal.user_no = usr.user_no) " + sql_query += "WHERE usr.username = $1;" - connection.query(sql_query) do |result| - usernames = result.field_values('username') + connection.query(sql_query, [user]) do |result| + if result.num_tuples > 0 + principal_id = result[0]['principal_id'] + end end connection.close() + rescue PGError => e # Pretend like we're database-agnostic in case we ever are. raise DatabaseError.new(e) end - return usernames + return principal_id end end diff --git a/lib/common/postfixadmin_plugin.rb b/lib/common/postfixadmin_plugin.rb index 5556d5a..b14f295 100644 --- a/lib/common/postfixadmin_plugin.rb +++ b/lib/common/postfixadmin_plugin.rb @@ -70,8 +70,6 @@ module PostfixadminPlugin @db_user, @db_pass) - # If address = goto, then the alias basically says, "really - # deliver to that address; it's not an alias." sql_query = 'SELECT username FROM mailbox;' connection.query(sql_query) do |result| users = result.field_values('username') @@ -114,4 +112,35 @@ module PostfixadminPlugin return usernames end + + def list_aliases() + # + # Get a list of all aliases, useful for testing. + # + aliases = [] + + # Just assume PostgreSQL for now. + begin + connection = PGconn.connect(@db_host, + @db_port, + @db_opts, + @db_tty, + @db_name, + @db_user, + @db_pass) + + sql_query = 'SELECT address,goto FROM alias;' + results = connection.query(sql_query) + results.each do |row| + aliases << row # row should be a hash + end + connection.close() + rescue PGError => e + # But pretend like we're database-agnostic in case we ever are. + raise DatabaseError.new(e) + end + + return aliases + end + end diff --git a/lib/common/roundcube_plugin.rb b/lib/common/roundcube_plugin.rb index 0f80f72..1f9805a 100644 --- a/lib/common/roundcube_plugin.rb +++ b/lib/common/roundcube_plugin.rb @@ -32,11 +32,12 @@ module RoundcubePlugin end - protected; - - def get_user_id(user) - user_id = nil + def list_users() + # Produce a list of Roundcube users. This is used in prune/rm, and + # is public because it is useful in testing. + usernames = [] + # Just assume PostgreSQL for now. begin connection = PGconn.connect(@db_host, @db_port, @@ -46,30 +47,25 @@ module RoundcubePlugin @db_user, @db_pass) - sql_query = "SELECT user_id FROM users WHERE username = $1;" - - connection.query(sql_query, [user]) do |result| - if result.num_tuples > 0 - user_id = result[0]['user_id'] - end + sql_query = "SELECT username FROM users;" + connection.query(sql_query) do |result| + usernames = result.field_values('username') end connection.close() - rescue PGError => e # Pretend like we're database-agnostic in case we ever are. raise DatabaseError.new(e) end - return user_id + return usernames end + protected; - # Used in both prune/rm. - def list_users() - usernames = [] + def get_user_id(user) + user_id = nil - # Just assume PostgreSQL for now. begin connection = PGconn.connect(@db_host, @db_port, @@ -79,18 +75,23 @@ module RoundcubePlugin @db_user, @db_pass) - sql_query = "SELECT username FROM users;" - connection.query(sql_query) do |result| - usernames = result.field_values('username') + sql_query = "SELECT user_id FROM users WHERE username = $1;" + + connection.query(sql_query, [user]) do |result| + if result.num_tuples > 0 + user_id = result[0]['user_id'] + end end connection.close() + rescue PGError => e # Pretend like we're database-agnostic in case we ever are. raise DatabaseError.new(e) end - return usernames + return user_id end + end diff --git a/test/rm_account_test.rb b/test/rm_account_test.rb deleted file mode 100644 index bd0ac30..0000000 --- a/test/rm_account_test.rb +++ /dev/null @@ -1,112 +0,0 @@ -require 'pg' -require 'test/unit' - -require 'common/configuration' - -class RmAccountTest < Test::Unit::TestCase - - TESTCONF_PATH = 'test/mailshears.test.conf.yml' - - def connect_superuser() - db_host = 'localhost' - db_port = 5432 - db_opts = nil - db_tty = nil - db_name = 'postgres' - db_user = 'postgres' - db_pass = nil - - connection = PGconn.connect(db_host, - db_port, - db_opts, - db_tty, - db_name, - db_user, - db_pass) - - return connection - end - - - def test_single_rm - cfg = Configuration.new(TESTCONF_PATH) - argv = ["adam@example.net"] - - # Load each of the plugins that we'll need. - cfg.plugins.each do |plugin_file| - require "rm/plugins/#{plugin_file}" - end - - # And the runners. - require "rm/rm_runner" - require "rm/rm_dummy_runner" - - require 'stringio' - output_buffer = StringIO.new() - - $stdout = output_buffer - plugin_class = RmPlugin.run(cfg, *argv) - $stdout = STDOUT - - actual = output_buffer.string() - - expected = "PostfixadminRm - Removed user: " + - "adam@example.net (adam@example.net)\n" + - "RoundcubeRm - Removed user: adam@example.net (User ID: 2)\n" + - "AgendavRm - Removed user: adam@example.net " + - "(Username: adam@example.net)\n" + - "DavicalRm - User not found: adam@example.net\n" - assert(actual == expected) - end - - def setup - # Create databases using from the test configuration file. - cfg = Configuration.new(TESTCONF_PATH) - connection = connect_superuser() - - cfg.plugins.each do |plugin| - plugin_dbname = cfg.send("#{plugin}_dbname") - query = "CREATE DATABASE #{plugin_dbname};" - connection.query(query) - - plugin_dbhost = cfg.send("#{plugin}_dbhost") - plugin_dbport = cfg.send("#{plugin}_dbport") - plugin_dbopts = cfg.send("#{plugin}_dbopts") - plugin_dbtty = cfg.send("#{plugin}_dbtty") - plugin_dbuser = cfg.send("#{plugin}_dbuser") - plugin_dbpass = cfg.send("#{plugin}_dbpass") - - plugin_conn = PGconn.connect(plugin_dbhost, - plugin_dbport, - plugin_dbopts, - plugin_dbtty, - plugin_dbname, - plugin_dbuser, - plugin_dbpass) - - sql = File.open("test/sql/#{plugin}.sql").read() - plugin_conn.query(sql) - sql = File.open("test/sql/#{plugin}-fixtures.sql").read() - plugin_conn.query(sql) - plugin_conn.close() - end - - connection.close() - end - - - def teardown - # Destroy databases using from the test configuration file. - cfg = Configuration.new(TESTCONF_PATH) - connection = connect_superuser() - - cfg.plugins.each do |plugin| - plugin_dbname = cfg.send("#{plugin}_dbname") - query = "DROP DATABASE #{plugin_dbname};" - connection.query(query) - end - - connection.close() - end - -end diff --git a/test/sql/README.fixtures b/test/sql/README.fixtures new file mode 100644 index 0000000..b4a242c --- /dev/null +++ b/test/sql/README.fixtures @@ -0,0 +1,113 @@ +Here's a quick (incomplete!) overview of what winds up in your tables. + + +1. agendav_test + ++----------------------------+ +| prefs | ++------------------+---------+ +| username | options | ++------------------+---------+ +| adam@example.net | herp | ++------------------+---------+ + + ++------------------------------------------------------+ +| shared | ++-----+------------------+----------+------------------+ +| sid | user_from | calendar | user_which | ++-----+------------------+----------+------------------+ +| 1 | adam@example.net | derp | beth@example.net | ++-----+------------------+----------+------------------+ + + +2. davical_test + ++-------------------------------------------------------+ +| usr | ++---------+--------+----------------+-------------------+ +| user_no | active | joined | username | ++---------+--------+----------------+-------------------+ +| 17 | t | 2014-01-04 ... | alice@example.com | ++---------+--------+----------------+-------------------+ + ++-----------------------------------------+ +| usr_setting | ++---------+--------------+----------------+ +| user_no | setting_name | setting_value | ++---------+--------------+----------------+ +| 17 | dumb setting | its dumb value | ++---------+--------------+----------------+ + + +3. postfixadmin_test + ++-------------+ +| domain | ++-------------+ +| domain | ++-------------+ +| ALL | ++-------------+ +| example.com | ++-------------+ +| example.net | ++-------------+ + + ++----------------------------------------------+ +| mailbox | ++-------------------+-------------+------------+ +| username | domain | local_part | ++-------------------+-------------+------------+ +| alice@example.com | example.com | alice | ++-------------------+-------------+------------+ +| bob@example.com | example.com | bob | ++-------------------+-------------+------------+ +| adam@example.net | example.net | adam | ++-------------------+-------------+------------+ +| beth@example.net | example.net | beth | ++-------------------+-------------+------------+ +| carol@example.net | example.net | carol | ++-------------------+-------------+------------+ + + ++------------------------------------------------------+ +| alias | ++-------------------+-------------------+--------------+ +| address | goto | domain | ++-------------------+-------------------+--------------+ +| alice@example.com | alice@example.com | example.com | ++-------------------+-------------------+--------------+ +| bob@example.com | bob@example.com | example.com | ++-------------------+-------------------+--------------+ +| adam@example.net | adam@example.net | example.net | ++-------------------+-------------------+--------------+ +| beth@example.net | beth@example.net | example.net | ++-------------------+-------------------+--------------+ +| carol@example.net | carol@example.net | example.net | ++-------------------+-------------------+--------------+ + + ++---------------------------------+ +| domain_admins | ++-------------------+-------------+ +| username | domain | ++-------------------+-------------+ +| admin@example.com | example.com | ++-------------------+-------------+ +| admin@example.com | example.net | ++-------------------+-------------+ + + + +4. roundcube_test + + ++---------+-------------------+ +| user_id | username | ++---------+-------------------+ +| 1 | alice@example.com | ++---------+-------------------+ +| 2 | adam@example.net | ++---------+-------------------+ diff --git a/test/test_rm.rb b/test/test_rm.rb new file mode 100644 index 0000000..01d2c1c --- /dev/null +++ b/test/test_rm.rb @@ -0,0 +1,213 @@ +require 'pg' +require 'stringio' +require 'test/unit' + +# WARNING: Test output is dependent on the order these classes include +# certain modules; i.e. on the 'require' order. +require 'common/configuration' +require "rm/plugins/agendav" +require "rm/plugins/davical" +require "rm/plugins/postfixadmin" +require "rm/plugins/roundcube" +require "rm/rm_runner" +require "rm/rm_dummy_runner" + + +class TestRm < Test::Unit::TestCase + + TESTCONF_PATH = 'test/mailshears.test.conf.yml' + + def connect_superuser() + db_host = 'localhost' + db_port = 5432 + db_opts = nil + db_tty = nil + db_name = 'postgres' + db_user = 'postgres' + db_pass = nil + + connection = PGconn.connect(db_host, + db_port, + db_opts, + db_tty, + db_name, + db_user, + db_pass) + + return connection + end + + def setup + # Create databases using from the test configuration file. + cfg = Configuration.new(TESTCONF_PATH) + connection = connect_superuser() + + cfg.plugins.each do |plugin| + plugin_dbname = cfg.send("#{plugin}_dbname") + query = "CREATE DATABASE #{plugin_dbname};" + connection.query(query) + + plugin_dbhost = cfg.send("#{plugin}_dbhost") + plugin_dbport = cfg.send("#{plugin}_dbport") + plugin_dbopts = cfg.send("#{plugin}_dbopts") + plugin_dbtty = cfg.send("#{plugin}_dbtty") + plugin_dbuser = cfg.send("#{plugin}_dbuser") + plugin_dbpass = cfg.send("#{plugin}_dbpass") + + plugin_conn = PGconn.connect(plugin_dbhost, + plugin_dbport, + plugin_dbopts, + plugin_dbtty, + plugin_dbname, + plugin_dbuser, + plugin_dbpass) + + sql = File.open("test/sql/#{plugin}.sql").read() + plugin_conn.query(sql) + sql = File.open("test/sql/#{plugin}-fixtures.sql").read() + plugin_conn.query(sql) + plugin_conn.close() + end + + connection.close() + end + + + + def test_rm_user + cfg = Configuration.new(TESTCONF_PATH) + argv = ["adam@example.net"] + + output_buffer = StringIO.new() + + $stdout = output_buffer + plugin_class = RmPlugin.run(cfg, *argv) + $stdout = STDOUT + + actual = output_buffer.string() + + expected = + "AgendavRm - Removed user: adam@example.net " + + "(Username: adam@example.net)\n" + + "DavicalRm - User not found: adam@example.net\n" + + "PostfixadminRm - Removed user: " + + "adam@example.net (adam@example.net)\n" + + "RoundcubeRm - Removed user: adam@example.net (User ID: 2)\n" + + assert_equal(expected, actual) + + # Now make sure the database has what we expect. + + arm = AgendavRm.new(cfg) + actual = arm.list_users() + expected = [] + assert_equal(expected, actual) + + drm = DavicalRm.new(cfg) + actual = drm.list_users() + expected = ['alice@example.com'] + assert_equal(expected, actual) + + pfarm = PostfixadminRm.new(cfg) + actual = pfarm.list_users() + expected = ['alice@example.com', + 'bob@example.com', + 'beth@example.net', + 'carol@example.net'] + assert_equal(expected, actual) + + actual = pfarm.list_domains() + expected = ['example.com', 'example.net'] + assert_equal(expected, actual) + + actual = pfarm.list_aliases() + expected = [{'address' => 'alice@example.com', + 'goto' => 'alice@example.com'}, + {'address' => 'bob@example.com', + 'goto' => 'bob@example.com'}, + {'address' => 'beth@example.net', + 'goto' => 'beth@example.net'}, + {'address' => 'carol@example.net', + 'goto' => 'carol@example.net'}] + assert_equal(expected, actual) + + rrm = RoundcubeRm.new(cfg) + actual = rrm.list_users() + expected = ['alice@example.com'] + assert_equal(expected, actual) + end + + + def test_rm_domain + # + # This must (and should) run after test_rm_user(). + # + cfg = Configuration.new(TESTCONF_PATH) + argv = ["example.net"] + + output_buffer = StringIO.new() + + $stdout = output_buffer + plugin_class = RmPlugin.run(cfg, *argv) + $stdout = STDOUT + + actual = output_buffer.string() + + expected = + "AgendavRm - Removed domain: example.net (example.net)\n" + + "DavicalRm - Domain not found: example.net\n" + + "PostfixadminRm - Removed domain: example.net (example.net)\n" + + "RoundcubeRm - Removed domain: example.net (example.net)\n" + + assert_equal(expected, actual) + + # Now make sure the database has what we expect. + + arm = AgendavRm.new(cfg) + actual = arm.list_users() + expected = [] + assert_equal(expected, actual) + + drm = DavicalRm.new(cfg) + actual = drm.list_users() + expected = ['alice@example.com'] + assert_equal(expected, actual) + + pfarm = PostfixadminRm.new(cfg) + actual = pfarm.list_users() + expected = ['alice@example.com', 'bob@example.com'] + assert_equal(expected, actual) + + actual = pfarm.list_domains() + expected = ['example.com'] + assert_equal(expected, actual) + + actual = pfarm.list_aliases() + expected = [{'address' => 'alice@example.com', + 'goto' => 'alice@example.com'}, + {'address' => 'bob@example.com', + 'goto' => 'bob@example.com'}] + assert_equal(expected, actual) + + rrm = RoundcubeRm.new(cfg) + actual = rrm.list_users() + expected = ['alice@example.com'] + assert_equal(expected, actual) + end + + + + def teardown + cfg = Configuration.new(TESTCONF_PATH) + connection = connect_superuser() + + cfg.plugins.each do |plugin| + plugin_dbname = cfg.send("#{plugin}_dbname") + query = "DROP DATABASE #{plugin_dbname};" + connection.query(query) + end + + connection.close() + end + +end -- 2.43.2 From 1159a8065414cf606b4fae524aedcc7063ab1eda Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Tue, 16 Sep 2014 20:11:55 -0400 Subject: [PATCH 07/16] Update the supported version of Roundcube. --- doc/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/README b/doc/README index 3537424..4657422 100644 --- a/doc/README +++ b/doc/README @@ -28,7 +28,7 @@ Right now, mailshears is targeted at one type of setup: FilesystemMailstore. * You maybe use Roundcube[3] webmail. Another optional - plugin. Specifically, roundcube-0.7.2 is supported at the moment. + plugin. Specifically, roundcube-1.0.2 is supported at the moment. 3. Installation -- 2.43.2 From 41b5e74bedf1def1ef22cd77ed26d87fae859a13 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Tue, 16 Sep 2014 20:52:44 -0400 Subject: [PATCH 08/16] Remove a superclass from DovecotMv that no longer exists. --- lib/mv/plugins/dovecot.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/mv/plugins/dovecot.rb b/lib/mv/plugins/dovecot.rb index 1b0ef79..b1dc598 100644 --- a/lib/mv/plugins/dovecot.rb +++ b/lib/mv/plugins/dovecot.rb @@ -1,9 +1,8 @@ require 'common/filesystem' -require 'common/mailstore' require 'common/dovecot_plugin' require 'mv/mv_plugin' -class DovecotMv < Mailstore +class DovecotMv include DovecotPlugin include MvPlugin -- 2.43.2 From 7e5de74d94665d7d4d32945df389c5d827119f95 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Tue, 16 Sep 2014 20:53:14 -0400 Subject: [PATCH 09/16] Add a preliminary PostfixadminMv. --- lib/mv/plugins/postfixadmin.rb | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 lib/mv/plugins/postfixadmin.rb diff --git a/lib/mv/plugins/postfixadmin.rb b/lib/mv/plugins/postfixadmin.rb new file mode 100644 index 0000000..223c716 --- /dev/null +++ b/lib/mv/plugins/postfixadmin.rb @@ -0,0 +1,61 @@ +require 'pg' + +require 'common/postfixadmin_plugin' +require 'mv/mv_plugin' + +class PostfixadminMv + + include PostfixadminPlugin + include MvPlugin + + + def mv_user(user_from, user_to) + raise NonexistentUserError.new(user_from) if not user_exists(user_from) + + user_to_parts = user_to.split('@') + localpart_to = user_to_parts[0] + domain_to = user_to_parts[1] + + sql_queries = ['UPDATE mailbox SET username=$1, + domain=$2, + maildir=$2/$3/, + local_part=$3 + WHERE username=$4;'] + + sql_queries = ['UPDATE alias SET address=$1, + domain=$2, + goto=REPLACE(goto, $4, $1); + WHERE address=$4;'] + + sql_queries = ['UPDATE alias SET goto=REPLACE(GOTO, $4, $1);'] + + begin + connection = PGconn.connect(@db_host, + @db_port, + @db_opts, + @db_tty, + @db_name, + @db_user, + @db_pass) + + sql_queries.each do |sql_query| + connection.query(sql_query, [user_to, + domain_to, + localpart_to, + user_from]) + end + + connection.close() + + rescue PGError => e + # Pretend like we're database-agnostic in case we ever are. + raise DatabaseError.new(e) + end + end + + + def mv_domain(domain_from, domain_to) + raise NotImplementedError + end + +end -- 2.43.2 From edf320a409cc942c632f7a64180887b7764c52c7 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 17 Sep 2014 11:56:09 -0400 Subject: [PATCH 10/16] Fix a SQL query in the AgendavMv plugin. --- lib/mv/plugins/agendav.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mv/plugins/agendav.rb b/lib/mv/plugins/agendav.rb index 112fa2a..d5858c0 100644 --- a/lib/mv/plugins/agendav.rb +++ b/lib/mv/plugins/agendav.rb @@ -14,7 +14,7 @@ class AgendavMv end def mv_user(from, to) - sql_queries = ['UPDATE prefs SET username = $1 WHERE username $2;'] + sql_queries = ['UPDATE prefs SET username = $1 WHERE username = $2;'] sql_queries << 'UPDATE shared SET user_from = $1 WHERE user_from = $2;' sql_queries << 'UPDATE shared SET user_which = $1 WHERE user_which = $2;' -- 2.43.2 From ca9ea0fe29615be543114431b26a057f5d6a2a6b Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 17 Sep 2014 11:56:47 -0400 Subject: [PATCH 11/16] Fix SQL query clobbering in PostfixadminMv. --- lib/mv/plugins/postfixadmin.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/mv/plugins/postfixadmin.rb b/lib/mv/plugins/postfixadmin.rb index 223c716..5cc34e3 100644 --- a/lib/mv/plugins/postfixadmin.rb +++ b/lib/mv/plugins/postfixadmin.rb @@ -22,12 +22,12 @@ class PostfixadminMv local_part=$3 WHERE username=$4;'] - sql_queries = ['UPDATE alias SET address=$1, + sql_queries << 'UPDATE alias SET address=$1, domain=$2, - goto=REPLACE(goto, $4, $1); - WHERE address=$4;'] + goto=REPLACE(goto, $4, $1) + WHERE address=$4;' - sql_queries = ['UPDATE alias SET goto=REPLACE(GOTO, $4, $1);'] + sql_queries << 'UPDATE alias SET goto=REPLACE(GOTO, $4, $1);' begin connection = PGconn.connect(@db_host, @@ -51,6 +51,7 @@ class PostfixadminMv # Pretend like we're database-agnostic in case we ever are. raise DatabaseError.new(e) end + end -- 2.43.2 From 00237603494fde1d1f62e8a4e68326e00abb5fa1 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Thu, 29 Oct 2015 20:45:57 -0400 Subject: [PATCH 12/16] Fix nil error when only postfixadmin is enabled. --- lib/common/plugin.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/common/plugin.rb b/lib/common/plugin.rb index 81e1fbb..272f0df 100644 --- a/lib/common/plugin.rb +++ b/lib/common/plugin.rb @@ -16,6 +16,7 @@ module Plugin end def includers + @includers ||= [] return @includers end -- 2.43.2 From b6c036d9c976116a414e5cbf3dd8e51629bdd219 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Thu, 29 Oct 2015 20:54:51 -0400 Subject: [PATCH 13/16] Remove "-K" from the shebang, and remove the outdated intro comment. --- bin/mailshears | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/bin/mailshears b/bin/mailshears index f7d5afa..a218b2b 100755 --- a/bin/mailshears +++ b/bin/mailshears @@ -1,17 +1,7 @@ -#!/usr/bin/ruby -wKU +#!/usr/bin/ruby -wU # # mailshears, to prune unused mail directories. # -# Mail users for virtual hosts are stored in SQL, and managed by -# Postfixadmin. However, the physical directories are handled by -# Postfix/Dovecot and are left untouched by Postfixadmin. This is good -# for security, but comes at a cost: Postfixadmin can't remove a -# user's mail directory when his or her account is deleted. -# -# This program compares the list of filesystem users with the ones -# in the database. It outputs any users that exist in the -# filesystem, but not the database. -# # Define a usage string using the program name. exe = File.basename($PROGRAM_NAME) -- 2.43.2 From 35dffd876681d82cb87d54c6a28b8cbdfcc28925 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Thu, 29 Oct 2015 20:55:29 -0400 Subject: [PATCH 14/16] Remove mv_domain() from MvPlugin. --- lib/mv/mv_plugin.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/mv/mv_plugin.rb b/lib/mv/mv_plugin.rb index 4b61603..29f697c 100644 --- a/lib/mv/mv_plugin.rb +++ b/lib/mv/mv_plugin.rb @@ -13,11 +13,6 @@ module MvPlugin return MvDummyRunner end - def mv_domain(from, to) - # Rename the given domain. - raise NotImplementedError - end - def mv_user(from, to) # Rename the given user. raise NotImplementedError -- 2.43.2 From 2b0b374200d46bc2219299ff7ab67fea2e0b9d03 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Thu, 29 Oct 2015 20:56:29 -0400 Subject: [PATCH 15/16] Clarify error class comments. --- lib/common/errors.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/common/errors.rb b/lib/common/errors.rb index d0bcfe2..bcae691 100644 --- a/lib/common/errors.rb +++ b/lib/common/errors.rb @@ -4,17 +4,16 @@ class DatabaseError < StandardError end -# Perhaps surprisingly, used to indicate that an user name is -# invalid. +# Username is syntactically invalid. class InvalidUserError < StandardError end -# Used to indicate that an user does not exist on the filesystem. +# Used to indicate that an user does not exist. class NonexistentUserError < StandardError end -# Used to indicate that a domain does not exist on the filesystem. +# Used to indicate that a domain does not exist. class NonexistentDomainError < StandardError end -- 2.43.2 From 8610295a5340a82a6c95f68fe983fe0ce5a2e382 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Thu, 29 Oct 2015 20:56:50 -0400 Subject: [PATCH 16/16] Disable domain moves in the dummy MvRunner. --- lib/mv/mv_dummy_runner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mv/mv_dummy_runner.rb b/lib/mv/mv_dummy_runner.rb index ff612ff..11f2ff5 100644 --- a/lib/mv/mv_dummy_runner.rb +++ b/lib/mv/mv_dummy_runner.rb @@ -7,7 +7,7 @@ class MvDummyRunner if src.include?('@') then puts "Would move user: #{src} to #{dst}" else - puts "Would move domain: #{src} to #{dst}" + raise NotImplementedError.new('Only users can be moved.') end end -- 2.43.2