postfix_admin 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +52 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +20 -0
  5. data/CHANGELOG.md +26 -12
  6. data/Gemfile +1 -1
  7. data/LICENSE +1 -1
  8. data/README.md +26 -20
  9. data/Rakefile +48 -1
  10. data/bin/console +6 -2
  11. data/db/reset.rb +7 -0
  12. data/db/seeds.rb +26 -0
  13. data/docker-admin/config.local.php +6 -1
  14. data/docker-app/Dockerfile +2 -6
  15. data/docker-app/my.cnf +2 -2
  16. data/docker-compose.yml +20 -20
  17. data/lib/postfix_admin/base.rb +119 -112
  18. data/lib/postfix_admin/cli.rb +267 -158
  19. data/lib/postfix_admin/doveadm.rb +32 -20
  20. data/lib/postfix_admin/{admin.rb → models/admin.rb} +25 -2
  21. data/lib/postfix_admin/{alias.rb → models/alias.rb} +16 -26
  22. data/lib/postfix_admin/{application_record.rb → models/application_record.rb} +1 -1
  23. data/lib/postfix_admin/{concerns → models/concerns}/existing_timestamp.rb +1 -2
  24. data/lib/postfix_admin/models/concerns/has_password.rb +16 -0
  25. data/lib/postfix_admin/models/domain.rb +112 -0
  26. data/lib/postfix_admin/models/domain_admin.rb +19 -0
  27. data/lib/postfix_admin/models/log.rb +20 -0
  28. data/lib/postfix_admin/models/mailbox.rb +126 -0
  29. data/lib/postfix_admin/models/quota2.rb +18 -0
  30. data/lib/postfix_admin/models.rb +8 -9
  31. data/lib/postfix_admin/runner.rb +91 -28
  32. data/lib/postfix_admin/version.rb +1 -1
  33. data/postfix_admin.gemspec +12 -10
  34. metadata +61 -34
  35. data/.github/workflows/ruby.yml +0 -37
  36. data/docker-app/docker-entrypoint.sh +0 -5
  37. data/docker-app-2.5/Dockerfile +0 -15
  38. data/lib/postfix_admin/concerns/dovecot_cram_md5_password.rb +0 -30
  39. data/lib/postfix_admin/domain.rb +0 -98
  40. data/lib/postfix_admin/domain_admin.rb +0 -8
  41. data/lib/postfix_admin/log.rb +0 -5
  42. data/lib/postfix_admin/mail_domain.rb +0 -9
  43. data/lib/postfix_admin/mailbox.rb +0 -89
  44. data/lib/postfix_admin/quota.rb +0 -6
  45. /data/lib/postfix_admin/{concerns → models/concerns}/.keep +0 -0
@@ -8,46 +8,41 @@ module PostfixAdmin
8
8
  attr_reader :config
9
9
 
10
10
  DEFAULT_CONFIG = {
11
- 'database' => 'mysql2://postfix:password@localhost/postfix',
12
- 'aliases' => 30,
13
- 'mailboxes' => 30,
14
- 'maxquota' => 100,
15
- 'scheme' => 'CRAM-MD5',
16
- 'passwordhash_prefix' => true
17
- }
11
+ "database" => "mysql2://postfix:password@localhost/postfix",
12
+ "aliases" => 30,
13
+ "mailboxes" => 30,
14
+ "maxquota" => 100,
15
+ "scheme" => "CRAM-MD5",
16
+ "passwordhash_prefix" => true
17
+ }.freeze
18
18
 
19
19
  def initialize(config)
20
20
  @config = {}
21
- @config[:database] = config['database']
22
- @config[:aliases] = config['aliases'] || 30
23
- @config[:mailboxes] = config['mailboxes'] || 30
24
- @config[:maxquota] = config['maxquota'] || 100
25
- @config[:scheme] = config['scheme'] || 'CRAM-MD5'
26
- @config[:passwordhash_prefix] = if config['passwordhash_prefix'].nil?
27
- true
21
+ @config[:database] = config["database"]
22
+ @config[:aliases] = config["aliases"] || DEFAULT_CONFIG["aliases"]
23
+ @config[:mailboxes] = config["mailboxes"] || DEFAULT_CONFIG["mailboxes"]
24
+ @config[:maxquota] = config["maxquota"] || DEFAULT_CONFIG["maxquota"]
25
+ @config[:scheme] = config["scheme"] || DEFAULT_CONFIG["scheme"]
26
+ @config[:passwordhash_prefix] = if config.has_key?("passwordhash_prefix")
27
+ config["passwordhash_prefix"]
28
28
  else
29
- config['passwordhash_prefix']
29
+ DEFAULT_CONFIG["passwordhash_prefix"]
30
30
  end
31
31
  end
32
32
 
33
33
  def db_setup
34
- raise "'database' parameter is required in '#{CLI.config_file}'" unless @config[:database]
34
+ database = ENV.fetch("DATABASE_URL") { @config[:database] }
35
35
 
36
- uri = URI.parse(@config[:database])
37
-
38
- if uri.scheme == "mysql"
39
- uri.scheme = "mysql2"
40
- warn("Deprecation Warning: Use 'mysql2' as a DB adopter instead of 'mysql' in '#{CLI.config_file}'")
36
+ unless database
37
+ raise_error "'database' parameter is required in '#{CLI.config_file}' or specify 'DATABASE_URL' environment variable"
41
38
  end
42
39
 
43
- if uri.scheme != "mysql2"
44
- raise "'#{uri.scheme}' is not supported as a DB adopter. Use 'mysql2' instead in '#{CLI.config_file}'."
45
- end
40
+ uri = URI.parse(database)
46
41
 
47
42
  ActiveRecord::Base.establish_connection(uri.to_s)
48
43
 
49
44
  rescue LoadError => e
50
- raise e.message
45
+ raise_error e.message
51
46
  end
52
47
 
53
48
  def add_admin_domain(user_name, domain_name)
@@ -57,11 +52,11 @@ module PostfixAdmin
57
52
  domain = Domain.find(domain_name)
58
53
 
59
54
  if admin.has_domain?(domain)
60
- raise Error, "#{user_name} is already registered as admin of #{domain_name}."
55
+ raise_error "Admin '#{user_name}' has already been registered for Domain '#{domain_name}'"
61
56
  end
62
57
 
63
58
  admin.rel_domains << domain
64
- admin.save or raise "Relation Error: Domain of Admin"
59
+ admin.save || raise_error("Relation Error: Domain of Admin")
65
60
  end
66
61
 
67
62
  def delete_admin_domain(user_name, domain_name)
@@ -71,181 +66,193 @@ module PostfixAdmin
71
66
  domain_admin_query = admin.domain_admins.where(domain: domain_name)
72
67
 
73
68
  unless domain_admin_query.take
74
- raise Error, "#{user_name} is not registered as admin of #{domain_name}."
69
+ raise_error "#{user_name} is not registered as admin of #{domain_name}."
75
70
  end
76
71
 
77
72
  domain_admin_query.delete_all
78
73
  end
79
74
 
80
75
  def add_admin(username, password)
81
- password_check(password)
76
+ validate_password(password)
82
77
 
83
78
  if Admin.exists?(username)
84
- raise Error, "#{username} is already registered as admin."
79
+ raise_error "Admin has already been registered: #{username}"
85
80
  end
81
+
86
82
  admin = Admin.new
87
83
  admin.attributes = {
88
84
  username: username,
89
- password: password,
85
+ password: password
90
86
  }
91
- unless admin.save
92
- raise "Could not save Admin #{admin.errors.map(&:to_s).join}"
93
- end
94
- end
95
-
96
- def add_account(address, password, in_name = nil)
97
- name = in_name || ''
98
- password_check(password)
99
-
100
- if address !~ /.+\@.+\..+/
101
- raise Error, "Invalid mail address #{address}"
102
- end
103
- user, domain_name = address_split(address)
104
- path = "#{domain_name}/#{address}/"
105
87
 
106
- unless Domain.exists?(domain_name)
107
- raise Error, "Could not find domain #{domain_name}"
108
- end
88
+ raise_save_error(admin) unless admin.save
89
+ end
109
90
 
110
- if Alias.exists?(address)
111
- raise Error, "#{address} is already registered."
112
- end
91
+ # Adds an email account that consists of a Mailbox and an Alias.
92
+ def add_account(address, password, name: "")
93
+ validate_account(address, password)
113
94
 
114
- domain = Domain.find(domain_name)
95
+ local_part, domain_name = address_split(address)
96
+ domain_must_exist!(domain_name)
115
97
 
116
98
  attributes = {
117
- username: address,
118
- password: password,
119
- name: name,
120
- maildir: path,
121
- local_part: user,
122
- quota_mb: @config[:maxquota]
99
+ local_part: local_part,
100
+ domain: domain_name,
101
+ password: password,
102
+ name: name,
103
+ quota: @config[:maxquota] * KB_TO_MB
123
104
  }
124
105
 
106
+ # An Alias also will be added when a Mailbox is saved.
125
107
  mailbox = Mailbox.new(attributes)
126
108
 
127
- domain.rel_mailboxes << mailbox
128
-
129
- unless domain.save
130
- raise "Could not save Mailbox and Domain #{mailbox.errors.map(&:to_s).join} #{domain.errors.map(&:to_s).join}"
131
- end
109
+ raise_save_error(mailbox) unless mailbox.save
132
110
  end
133
111
 
134
112
  def add_alias(address, goto)
135
113
  if Mailbox.exists?(address)
136
- raise Error, "mailbox #{address} is already registered!"
137
- end
138
- if Alias.exists?(address)
139
- raise Error, "alias #{address} is already registered!"
114
+ raise_error "Mailbox has already been registered: #{address}"
140
115
  end
141
116
 
142
- local_part, domain_name = address_split(address)
117
+ alias_must_not_exist!(address)
143
118
 
144
- unless Domain.exists?(domain_name)
145
- raise Error, "Invalid domain! #{domain_name}"
146
- end
119
+ local_part, domain_name = address_split(address)
147
120
 
148
- domain = Domain.find(domain_name)
121
+ domain = find_domain(domain_name)
149
122
 
150
123
  attributes = {
151
- local_part: local_part,
124
+ address: address,
152
125
  goto: goto
153
126
  }
127
+
154
128
  domain.rel_aliases << Alias.new(attributes)
155
- domain.save or raise "Could not save Alias"
129
+
130
+ raise_save_error(domain) unless domain.save
156
131
  end
157
132
 
158
133
  def delete_alias(address)
159
134
  if Mailbox.exists?(address)
160
- raise Error, "Can not delete mailbox by delete_alias. Use delete_account"
135
+ raise_error "Can not delete mailbox by delete_alias. Use delete_account"
161
136
  end
162
137
 
163
138
  unless Alias.exists?(address)
164
- raise Error, "#{address} is not found!"
139
+ raise_error "#{address} is not found!"
165
140
  end
166
141
 
167
142
  Alias.where(address: address).delete_all
168
143
  end
169
144
 
170
- def add_domain(domain_name)
145
+ def add_domain(domain_name, description: nil)
171
146
  domain_name = domain_name.downcase
172
- if domain_name !~ /.+\..+/
173
- raise Error, "Ivalid domain! #{domain_name}"
147
+
148
+ unless valid_domain_name?(domain_name)
149
+ raise_error "Invalid domain name: #{domain_name}"
174
150
  end
151
+
175
152
  if Domain.exists?(domain_name)
176
- raise Error, "#{domain_name} is already registered!"
153
+ raise_error "Domain has already been registered: #{domain_name}"
177
154
  end
155
+
156
+ new_description = description || domain_name
157
+
178
158
  domain = Domain.new
179
159
  domain.attributes = {
180
160
  domain: domain_name,
181
- description: domain_name,
161
+ description: new_description,
182
162
  aliases: @config[:aliases],
183
163
  mailboxes: @config[:mailboxes],
184
- maxquota: @config[:maxquota],
164
+ maxquota: @config[:maxquota]
185
165
  }
186
166
  domain.save!
187
167
  end
188
168
 
189
169
  def delete_domain(domain_name)
190
170
  domain_name = domain_name.downcase
191
- unless Domain.exists?(domain_name)
192
- raise Error, "Could not find domain #{domain_name}"
193
- end
194
171
 
195
- domain = Domain.find(domain_name)
196
- domain.rel_mailboxes.delete_all
197
- domain.rel_aliases.delete_all
172
+ domain = find_domain(domain_name)
198
173
 
199
174
  admin_names = domain.admins.map(&:username)
200
175
 
201
- domain.admins.delete_all
202
-
203
- admin_names.each do |name|
204
- next unless Admin.exists?(name)
205
-
206
- admin = Admin.find(name)
207
-
208
- # check if the admin is needed or not
209
- if admin.rel_domains.empty?
210
- admin.destroy
211
- end
212
- end
213
-
214
- domain.destroy
176
+ domain.destroy!
215
177
  end
216
178
 
217
179
  def delete_admin(user_name)
218
180
  unless Admin.exists?(user_name)
219
- raise Error, "Could not find admin #{user_name}"
181
+ raise_error "Could not find admin #{user_name}"
220
182
  end
221
183
 
222
184
  admin = Admin.find(user_name)
223
- admin.rel_domains.delete_all
224
185
  admin.destroy!
225
186
  end
226
187
 
227
188
  def delete_account(address)
228
189
  unless Alias.exists?(address) && Mailbox.exists?(address)
229
- raise Error, "Could not find account #{address}"
190
+ raise_error "Could not find account: #{address}"
230
191
  end
231
192
 
232
- Mailbox.where(username: address).delete_all
233
- Alias.where(address: address).delete_all
193
+ mailbox = Mailbox.find(address)
194
+ mailbox.destroy!
195
+ end
196
+
197
+ private
198
+
199
+ def find_domain(domain_name)
200
+ domain_must_exist!(domain_name)
201
+ Domain.find(domain_name)
202
+ end
203
+
204
+ def raise_error(message)
205
+ raise PostfixAdmin::Error, message
206
+ end
207
+
208
+ def raise_save_error(obj)
209
+ raise_error "Failed to save #{obj.class}: #{obj.errors.full_messages.join(', ')}"
234
210
  end
235
211
 
236
212
  def address_split(address)
237
213
  address.split('@')
238
214
  end
239
215
 
240
- private
216
+ def valid_domain_name?(domain_name)
217
+ /.+\..+/.match?(domain_name)
218
+ end
219
+
220
+ def valid_email_address?(address)
221
+ /.+@.+\..+/.match?(address)
222
+ end
223
+
224
+ def domain_must_exist!(domain_name)
225
+ unless Domain.exists?(domain_name)
226
+ raise_error "Could not find domain: #{domain_name}"
227
+ end
228
+ end
229
+
230
+ def alias_must_not_exist!(address)
231
+ if Alias.exists?(address)
232
+ raise_error "Alias has already been registered: #{address}"
233
+ end
234
+ end
241
235
 
242
236
  def admin_domain_check(user_name, domain_name)
243
- raise Error, "#{user_name} is not registered as admin." unless Admin.exists?(user_name)
244
- raise Error, "Could not find domain #{domain_name}" unless Domain.exists?(domain_name)
237
+ unless Admin.exists?(user_name)
238
+ raise_error "#{user_name} is not registered as admin."
239
+ end
240
+
241
+ domain_must_exist!(domain_name)
245
242
  end
246
243
 
247
- def password_check(password)
248
- raise Error, "Empty password" if password.nil? || password.empty?
244
+ def validate_password(password)
245
+ raise_error "Empty password" if password.nil? || password.empty?
246
+ end
247
+
248
+ def validate_account(address, password)
249
+ validate_password(password)
250
+
251
+ unless valid_email_address?(address)
252
+ raise_error "Invalid email address: #{address}"
253
+ end
254
+
255
+ alias_must_not_exist!(address)
249
256
  end
250
257
  end
251
258
  end