sugoi-mail 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +35 -0
- data/Rakefile +10 -0
- data/app/apis/mailservice_api.rb +178 -0
- data/app/controllers/.sugoi_admin_controller.rb.swp +0 -0
- data/app/controllers/account_controller.rb +39 -0
- data/app/controllers/address_controller.rb +49 -0
- data/app/controllers/application.rb +4 -0
- data/app/controllers/commandline_controller.rb +15 -0
- data/app/controllers/domain_controller.rb +37 -0
- data/app/controllers/mailinglist_controller.rb +51 -0
- data/app/controllers/mailservice_controller.rb +497 -0
- data/app/controllers/sugoi_admin_controller.rb +93 -0
- data/app/helpers/account_helper.rb +2 -0
- data/app/helpers/address_helper.rb +2 -0
- data/app/helpers/application_helper.rb +3 -0
- data/app/helpers/domain_helper.rb +2 -0
- data/app/helpers/mailservice_helper.rb +2 -0
- data/app/helpers/sugoi_admin_helper.rb +2 -0
- data/app/models/address.rb +110 -0
- data/app/models/admin_message.rb +45 -0
- data/app/models/confirmationcode.rb +59 -0
- data/app/models/domain.rb +76 -0
- data/app/models/mailinglist.rb +336 -0
- data/app/models/mailinglist_class.rb +39 -0
- data/app/models/message.rb +293 -0
- data/app/models/proxy_link.rb +25 -0
- data/app/models/user.rb +150 -0
- data/app/views/account/login.rhtml +22 -0
- data/app/views/account/logout.rhtml +10 -0
- data/app/views/account/signup.rhtml +17 -0
- data/app/views/account/welcome.rhtml +13 -0
- data/app/views/address/_form.rhtml +7 -0
- data/app/views/address/edit.rhtml +10 -0
- data/app/views/address/list.rhtml +27 -0
- data/app/views/address/new.rhtml +8 -0
- data/app/views/address/show.rhtml +8 -0
- data/app/views/domain/login.rhtml +22 -0
- data/app/views/domain/logout.rhtml +10 -0
- data/app/views/domain/signup.rhtml +17 -0
- data/app/views/domain/welcome.rhtml +13 -0
- data/app/views/layouts/address.rhtml +13 -0
- data/app/views/layouts/scaffold.rhtml +13 -0
- data/app/views/mailinglist/_form.rhtml +28 -0
- data/app/views/mailinglist/edit.rhtml +10 -0
- data/app/views/mailinglist/list.rhtml +27 -0
- data/app/views/mailinglist/new.rhtml +8 -0
- data/app/views/mailinglist/show.rhtml +16 -0
- data/app/views/sugoi_admin/create_domain.rhtml +1 -0
- data/app/views/sugoi_admin/list_addresses.rhtml +1 -0
- data/app/views/sugoi_admin/list_domains.rhtml +2 -0
- data/app/views/sugoi_admin/list_mailinglists.rhtml +1 -0
- data/bin/mailc +32 -0
- data/bin/maild +133 -0
- data/bin/sugoi-admin +21 -0
- data/bin/sugoi-mail +20 -0
- data/config/boot.rb +44 -0
- data/config/environment.rb +54 -0
- data/config/environments/bench.rb +21 -0
- data/config/environments/coverage.rb +21 -0
- data/config/environments/development.rb +21 -0
- data/config/environments/production.rb +18 -0
- data/config/environments/test.rb +19 -0
- data/config/lighttpd.conf +46 -0
- data/config/routes.rb +29 -0
- data/db/migrate/001_mailproxy.rb +7 -0
- data/db/migrate/002_create_users.rb +13 -0
- data/db/migrate/003_create_mailinglists.rb +13 -0
- data/db/migrate/004_create_addresses.rb +12 -0
- data/db/migrate/005_create_addresses_mailinglists.rb +13 -0
- data/db/migrate/006_alter_mailinglists.rb +9 -0
- data/db/migrate/007_create_messages.rb +25 -0
- data/db/migrate/008_add_mailinglistid_to_users.rb +14 -0
- data/db/migrate/009_add_domainadmin_to_users.rb +9 -0
- data/db/migrate/010_add_domain_to_users.rb +16 -0
- data/db/migrate/011_add_active_to_addresses.rb +14 -0
- data/db/migrate/012_create_confirmationcodes.rb +14 -0
- data/db/migrate/013_add_description_to_mailinglists.rb +9 -0
- data/db/migrate/014_create_admin_messages.rb +69 -0
- data/db/migrate/015_add_messages_to_mailinglists.rb +26 -0
- data/db/migrate/016_add_mailinglist_admin_to_users.rb +9 -0
- data/db/migrate/017_add_mailinglist_types.rb +94 -0
- data/db/migrate/018_add_bounciness_to_addresses.rb +20 -0
- data/db/migrate/019_add_archived_to_mailinglist_classes.rb +25 -0
- data/db/migrate/020_add_envelope_data_to_messages.rb +11 -0
- data/db/migrate/021_add_addresses_users.rb +14 -0
- data/db/migrate/022_add_virtual_to_users.rb +14 -0
- data/db/migrate/023_drop_openposting_from_mailinglists.rb +9 -0
- data/db/migrate/024_add_proxy_links.rb +21 -0
- data/db/migrate/025_add_proxify_to_mailinglist_classes.rb +25 -0
- data/db/schema.mysql.sql +104 -0
- data/db/schema.postgresql.sql +104 -0
- data/db/schema.rb +85 -0
- data/db/schema.sqlite.sql +104 -0
- data/db/schema.sqlserver.sql +113 -0
- data/db/schema_version +1 -0
- data/doc/README_FOR_APP +179 -0
- data/doc/mailinglist_classes description.txt +28 -0
- data/installer/rails_installer_defaults.yml +5 -0
- data/lib/daemonize.rb +56 -0
- data/lib/domain_system.rb +87 -0
- data/lib/gurgitate-rules.rb +69 -0
- data/lib/limitedfork.rb +66 -0
- data/lib/login_system.rb +87 -0
- data/public/.htaccess +40 -0
- data/public/404.html +8 -0
- data/public/500.html +8 -0
- data/public/dispatch.cgi +10 -0
- data/public/dispatch.fcgi +24 -0
- data/public/dispatch.rb +10 -0
- data/public/favicon.ico +0 -0
- data/public/images/rails.png +0 -0
- data/public/javascripts/application.js +2 -0
- data/public/javascripts/controls.js +815 -0
- data/public/javascripts/dragdrop.js +913 -0
- data/public/javascripts/effects.js +958 -0
- data/public/javascripts/prototype.js +2006 -0
- data/public/robots.txt +1 -0
- data/public/stylesheets/scaffold.css +74 -0
- data/public/stylesheets/trestle.css +74 -0
- data/script/about +3 -0
- data/script/breakpointer +3 -0
- data/script/console +3 -0
- data/script/destroy +3 -0
- data/script/fakedeliver +19 -0
- data/script/generate +3 -0
- data/script/performance/benchmarker +3 -0
- data/script/performance/profiler +3 -0
- data/script/plugin +3 -0
- data/script/process/reaper +3 -0
- data/script/process/spawner +3 -0
- data/script/runner +3 -0
- data/script/server +3 -0
- data/sugoi-mail.gemspec +36 -0
- data/test/fixtures/addresses.yml +70 -0
- data/test/fixtures/addresses_mailinglists.yml +20 -0
- data/test/fixtures/admin_messages.yml +65 -0
- data/test/fixtures/confirmationcodes.yml +13 -0
- data/test/fixtures/domains.yml +9 -0
- data/test/fixtures/mailinglist_classes.yml +45 -0
- data/test/fixtures/mailinglists.yml +80 -0
- data/test/fixtures/messages.yml +154 -0
- data/test/fixtures/proxy_links.yml +5 -0
- data/test/fixtures/users.yml +50 -0
- data/test/functional/domain_controller_test.rb +74 -0
- data/test/functional/mailservice_controller_test.rb +546 -0
- data/test/integration/test_soap.rb +413 -0
- data/test/integration/test_xmlrpc.rb +198 -0
- data/test/mocks/test/net-smtp-stub.rb +24 -0
- data/test/test_helper.rb +28 -0
- data/test/unit/address_test.rb +44 -0
- data/test/unit/admin_message_test.rb +41 -0
- data/test/unit/confirmationcode_test.rb +64 -0
- data/test/unit/domain_test.rb +128 -0
- data/test/unit/mailinglist_class_test.rb +82 -0
- data/test/unit/mailinglist_test.rb +145 -0
- data/test/unit/message_test.rb +151 -0
- data/test/unit/user_test.rb +126 -0
- data/test/units.rb +4 -0
- data/vendor/plugins/active_command/init.rb +1 -0
- data/vendor/plugins/active_command/lib/active_command/request.rb +137 -0
- data/vendor/plugins/active_command/lib/active_command/response.rb +132 -0
- data/vendor/plugins/active_command/lib/active_command.rb +2 -0
- data/vendor/plugins/ar_fixtures/CHANGELOG +10 -0
- data/vendor/plugins/ar_fixtures/MIT-LICENSE +20 -0
- data/vendor/plugins/ar_fixtures/README +19 -0
- data/vendor/plugins/ar_fixtures/Rakefile +54 -0
- data/vendor/plugins/ar_fixtures/about.yml +7 -0
- data/vendor/plugins/ar_fixtures/init.rb +1 -0
- data/vendor/plugins/ar_fixtures/lib/ar_fixtures.rb +102 -0
- data/vendor/plugins/ar_fixtures/tasks/ar_fixtures.rake +39 -0
- data/vendor/plugins/ar_fixtures/test/ar_fixtures_test.rb +53 -0
- data/vendor/plugins/ar_fixtures/test/database.yml +18 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/beer.rb +5 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/beers.yml +9 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/beers_drunkards.yml +8 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/drunkard.rb +6 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/drunkards.yml +8 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/glass.rb +2 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/glasses.yml +9 -0
- data/vendor/plugins/ar_fixtures/test/schema.rb +21 -0
- data/vendor/plugins/ar_fixtures/test/test_helper.rb +37 -0
- metadata +320 -0
@@ -0,0 +1,293 @@
|
|
1
|
+
require "date"
|
2
|
+
require "gurgitate/mailmessage"
|
3
|
+
require "net/smtp"
|
4
|
+
require "socket"
|
5
|
+
# require "pp"
|
6
|
+
|
7
|
+
# The Message class is one of the most important classes in
|
8
|
+
# sugoi-mail--mainly because it *does* so much. But anyway. Herewith a
|
9
|
+
# Model With Some Controller-like Things In It.
|
10
|
+
#
|
11
|
+
# This is the class that handles what a "message" is, and how it
|
12
|
+
# behaves. It makes considerable use of gurgitate-mail's mail-parsing
|
13
|
+
# library, so it might not be a bad idea to familiarize yourself with
|
14
|
+
# that if trying to work on this.
|
15
|
+
class Message < ActiveRecord::Base
|
16
|
+
belongs_to :mailinglist
|
17
|
+
belongs_to :address
|
18
|
+
acts_as_tree :order => "timestamp"
|
19
|
+
# has_and_belongs_to_many :mailinglists
|
20
|
+
|
21
|
+
class << self
|
22
|
+
|
23
|
+
private
|
24
|
+
def get_or_create_messageid mess
|
25
|
+
unless mess.headers["Message-Id"]
|
26
|
+
mess.headers["Message-Id"] = "<"+`uuidgen || uuid`.chomp +
|
27
|
+
"@" + Socket::gethostbyname("localhost")[0] + ">"
|
28
|
+
end
|
29
|
+
mess.headers["Message-Id"][0].contents
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_or_create_timestamp mess
|
33
|
+
if mess.headers["Date"] then
|
34
|
+
DateTime.parse(mess.headers["Date"][0].contents)
|
35
|
+
else
|
36
|
+
Time.now
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_parent mess
|
41
|
+
referenced=nil
|
42
|
+
if mess.headers["In-Reply-To"] then
|
43
|
+
referenced=mess.headers["In-Reply-To"][0].contents .
|
44
|
+
match(/(\<[^>]+\>)/)
|
45
|
+
if referenced then
|
46
|
+
referenced=referenced[1]
|
47
|
+
else
|
48
|
+
if mess.headers["References"] then
|
49
|
+
referenced=mess.headers["References"][0].contents.
|
50
|
+
split.last
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
Message.find_by_messageid referenced
|
55
|
+
end
|
56
|
+
|
57
|
+
public
|
58
|
+
# Processes a Gurgitate::Mailmessage object or the text of a
|
59
|
+
# message and creates a new Message object.
|
60
|
+
#
|
61
|
+
# +mess+:: Either a Gurgitate::Mailmessage object (with envelope
|
62
|
+
# information intact), or the text of a mail message (in which
|
63
|
+
# case the envelope information must be supplied)
|
64
|
+
# +recipient+:: The envelope recipient (SMTP's RCPT To:) of the mail
|
65
|
+
# message.
|
66
|
+
# +sender+:: The envelope sender (SMTP's MAIL From:) of the mail
|
67
|
+
# message.
|
68
|
+
def from_message(mess, recipient=nil, sender=nil)
|
69
|
+
# This method is way too long, sorry
|
70
|
+
if Gurgitate::Mailmessage === mess then
|
71
|
+
message = self.new
|
72
|
+
message.messageid = get_or_create_messageid mess
|
73
|
+
message.timestamp = get_or_create_timestamp mess
|
74
|
+
addr = Address.find_or_create_by_address mess.from
|
75
|
+
if addr.new_record? then addr.save end
|
76
|
+
|
77
|
+
message.address = addr
|
78
|
+
message.headers = mess.headers.to_s
|
79
|
+
message.body = mess.body.to_s
|
80
|
+
message.parent = find_parent mess
|
81
|
+
message.envelope_from = mess.from
|
82
|
+
message.envelope_to = mess.to.map { |t| t.contents }
|
83
|
+
|
84
|
+
message.address.save if message.address.new_record?
|
85
|
+
|
86
|
+
ml, type = Mailinglist.find_by_address(message.envelope_to[0])
|
87
|
+
|
88
|
+
if type == :mail and ml then
|
89
|
+
message.mailinglist = ml
|
90
|
+
end
|
91
|
+
|
92
|
+
return message
|
93
|
+
|
94
|
+
elsif String === mess then
|
95
|
+
from_message Gurgitate::Mailmessage.new(mess),recipient,sender
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
alias :parse :from_message
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# Extracts the bare email addresses from a header, without the extra
|
105
|
+
# commentary like the real names.
|
106
|
+
def pull_out_addresses header
|
107
|
+
if header
|
108
|
+
header.contents.split(/,/).map do |addr|
|
109
|
+
Address.parse(addr)[0]
|
110
|
+
end
|
111
|
+
else
|
112
|
+
[ ]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the proxified replacement for a header, with regular email
|
117
|
+
# addresses replaced by proxy addresses.
|
118
|
+
def proxify_header header, addr
|
119
|
+
unless Mailinglist.find_by_address addr
|
120
|
+
if proxy_address = Address.proxyaddress(addr)
|
121
|
+
header[0] . contents . sub( Regexp.new(Regexp.escape(addr) ),
|
122
|
+
proxy_address )
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def proxified_plain_addr header, addr
|
128
|
+
matches = addr.match(/(.*?)#(.*?)@(.*)/)
|
129
|
+
if matches then
|
130
|
+
localpart, destdomain, msgs_domain = matches[1..3]
|
131
|
+
to_address = "#{localpart}@#{destdomain}"
|
132
|
+
if Domain.find_by_name(msgs_domain) then
|
133
|
+
header[0] . contents .
|
134
|
+
sub(Regexp.new(Regexp.escape(addr)), to_address)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def proxify_mail_to_virtual_users
|
140
|
+
# special-case closed mail to mailing lists owned by virtual users
|
141
|
+
if mailinglist
|
142
|
+
if mailinglist.closed?
|
143
|
+
if Mailinglist.find_by_address(envelope_to)[0].user.
|
144
|
+
mailinglist.has_address? envelope_from then
|
145
|
+
if mailinglist.user.description then
|
146
|
+
@mess.headers["From"] = "=?UTF-8?B?" +
|
147
|
+
[ mailinglist.user.description ].pack("m").chomp +
|
148
|
+
"?= <" + mailinglist.user.address + ">"
|
149
|
+
else
|
150
|
+
@mess.headers["From"] = mailinglist.user.address
|
151
|
+
end
|
152
|
+
# @mess.headers.instance_variable_set("@headers_changed",true)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def proxify_proxifiable_mailing_list
|
159
|
+
addr=Address.find_or_create_by_address @mess.from
|
160
|
+
if addr.new_record? then addr.save end
|
161
|
+
|
162
|
+
if addr.outside? then
|
163
|
+
if mailinglist
|
164
|
+
if mailinglist.proxify?
|
165
|
+
if @mess.headers["From"] then
|
166
|
+
@mess.headers["Reply-To"] = addr.proxified mailinglist
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def proxify_normal_addresses
|
174
|
+
%w{From To Cc}.each do |header_name|
|
175
|
+
if @mess.headers[header_name] then
|
176
|
+
@mess.headers[header_name].map do |header|
|
177
|
+
pull_out_addresses header
|
178
|
+
end.flatten.each do |addr|
|
179
|
+
if newhdr = proxify_header(@mess.headers[header_name], addr)
|
180
|
+
@mess.headers[header_name] = newhdr
|
181
|
+
end
|
182
|
+
|
183
|
+
if newhdr = proxified_plain_addr(@mess.headers[header_name],
|
184
|
+
addr)
|
185
|
+
@mess.headers[header_name] = newhdr
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
# Rewrites the From:, To: and Cc: headers in the current message
|
194
|
+
# to reflect mail proxy addresses instead of "real" addresses.
|
195
|
+
def proxify_addresses
|
196
|
+
@mess = Gurgitate::Mailmessage.new to_s
|
197
|
+
|
198
|
+
proxify_normal_addresses
|
199
|
+
proxify_mail_to_virtual_users
|
200
|
+
proxify_proxifiable_mailing_list
|
201
|
+
|
202
|
+
@mess.to_s
|
203
|
+
end
|
204
|
+
|
205
|
+
public
|
206
|
+
|
207
|
+
# Returns the text of the message that would be sent out by the
|
208
|
+
# "deliver" message. This might have headers rewritten to reflect
|
209
|
+
# mail aliases.
|
210
|
+
def messagetext(smtpdetails={})
|
211
|
+
if smtpdetails.length == 1 and smtpdetails[0].has_key? :custom_message
|
212
|
+
smtpdetails[0][:custom_message]
|
213
|
+
end
|
214
|
+
|
215
|
+
if mailinglist.canonical_address? or mailinglist.closed? then
|
216
|
+
proxify_addresses
|
217
|
+
else
|
218
|
+
to_s
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def proxy_deliver(from_address, to_address)
|
223
|
+
text = proxify_addresses
|
224
|
+
|
225
|
+
smtpserver = "localhost"
|
226
|
+
smtpport = 25
|
227
|
+
|
228
|
+
Net::SMTP.start(smtpserver, smtpport, "localhost") do |smtp|
|
229
|
+
smtp.send_message text, from_address, to_address
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
# Delivers a message with the optional parameter hash +smtpdetails+.
|
235
|
+
#
|
236
|
+
#
|
237
|
+
def deliver(*smtpdetails)
|
238
|
+
if Hash === smtpdetails[0] then
|
239
|
+
smtpdetails = smtpdetails[0]
|
240
|
+
else
|
241
|
+
smtpdetails = {}
|
242
|
+
end
|
243
|
+
|
244
|
+
if mailinglist.closed?
|
245
|
+
if mailinglist.user.mailinglist.has_address? envelope_from or
|
246
|
+
mailinglist.user.address == envelope_from then
|
247
|
+
end
|
248
|
+
unless mailinglist.user.mailinglist.has_address? self.envelope_from
|
249
|
+
return bounce
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
if mailinglist.confirmation?
|
254
|
+
addresses = mailinglist.confirmed_addresses.map { |a| a.address }
|
255
|
+
else
|
256
|
+
addresses = mailinglist.expand_addresses.map { |a| a.address }
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
# Hm, not sure why this is *here* instead of somewhere
|
261
|
+
# else. Fix later.
|
262
|
+
#
|
263
|
+
# (This should actually be in the database somewhere, I'm thinking.)
|
264
|
+
smtpserver = "localhost"
|
265
|
+
smtpport = 25
|
266
|
+
|
267
|
+
if smtpdetails.has_key? :smtpserver then
|
268
|
+
smtpserver = smtpdetails[:smtpserver]
|
269
|
+
end
|
270
|
+
if smtpdetails.has_key? :smtpport then
|
271
|
+
smtpport = smtpdetails[:smtpport] || 25
|
272
|
+
end
|
273
|
+
|
274
|
+
fromaddress = "#{mailinglist.name}-bounces@#{mailinglist.user.domain.name}"
|
275
|
+
Net::SMTP.start(smtpserver, smtpport, "localhost") do |smtp|
|
276
|
+
# puts "Will send email to #{addresses.inspect}"
|
277
|
+
# puts "Message id is #{id}"
|
278
|
+
smtp.send_message messagetext, fromaddress, addresses
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def bounce
|
283
|
+
# TODO
|
284
|
+
nil
|
285
|
+
end
|
286
|
+
|
287
|
+
# Returns the text of the current message
|
288
|
+
def to_s
|
289
|
+
headers.to_s.chomp.chomp+"\n\n"+body.to_s
|
290
|
+
end
|
291
|
+
|
292
|
+
alias responses children # "children" comes from acts_as_tree
|
293
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class ProxyLink < ActiveRecord::Base
|
2
|
+
belongs_to :mailinglist
|
3
|
+
belongs_to :address
|
4
|
+
validates_presence_of :mailinglist
|
5
|
+
validates_presence_of :address
|
6
|
+
|
7
|
+
def proxy_address
|
8
|
+
"#{mailinglist.name}-#{id}@#{mailinglist.domain.name}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.real_address(addrstr)
|
12
|
+
matches=addrstr.match(/(.+)-([\d]+)@(.*)/)
|
13
|
+
if matches
|
14
|
+
mlname, proxyid, domainname = matches[1..3]
|
15
|
+
domain=Domain.find_by_name domainname
|
16
|
+
ml=Mailinglist.find_all_by_name(mlname).find do |m|
|
17
|
+
m.domain == domain
|
18
|
+
end
|
19
|
+
pl=self.find(proxyid.to_i)
|
20
|
+
if pl.mailinglist_id == ml.id then
|
21
|
+
return pl.address.address
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/app/models/user.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
# = The User class
|
5
|
+
#
|
6
|
+
# This is actually based on the user class that is generated
|
7
|
+
# automatically by the Login Generator, but I suspect only software
|
8
|
+
# historians need to know that. The Login Generator is pretty much
|
9
|
+
# universally adapted by anyone writing a Rails application that has
|
10
|
+
# users, and this one is no different.
|
11
|
+
#
|
12
|
+
# From the schema, the data elements of the User class (because
|
13
|
+
# otherwise, they wouldn't be described anywhere).
|
14
|
+
#
|
15
|
+
# +domain+:: The Domain that the user belongs to.
|
16
|
+
# +login+:: The user's username. This is also the name used
|
17
|
+
# for their Sugoi-Mail address.
|
18
|
+
# +password+:: A SHA1-encrypted password hash.
|
19
|
+
# +mailinglist+:: The user's Mailinglist. The addresses listed in
|
20
|
+
# this Mailinglist are the address that any message
|
21
|
+
# sent to +login@domain+ is then forwarded to.
|
22
|
+
# +domainadmin+:: Whether the user is a domain administrator or not.
|
23
|
+
# This mainly controls the ability to create new
|
24
|
+
# users.
|
25
|
+
# +mailinglistadmin+:: Whether they can create, delete, or modify
|
26
|
+
# mailing lists.
|
27
|
+
#
|
28
|
+
# The User's email address is actually provided by the Mailinglist that
|
29
|
+
# belongs to the user. By adding at least one Address to this Mailinglist,
|
30
|
+
# you can forward messages sent to user@sugoi-domain to their real email
|
31
|
+
# address. Any message sent from the user's real address via
|
32
|
+
# sugoi-domain has that real email address rewritten to be the
|
33
|
+
# sugoi-domain address.
|
34
|
+
|
35
|
+
class User < ActiveRecord::Base
|
36
|
+
|
37
|
+
#----------------------------------------
|
38
|
+
# First of all, email address stuff
|
39
|
+
#----------------------------------------
|
40
|
+
has_many :mailinglists
|
41
|
+
belongs_to :mailinglist
|
42
|
+
belongs_to :domain
|
43
|
+
|
44
|
+
# The list of addresses that mail sent to the user will be forwarded
|
45
|
+
# to.
|
46
|
+
def addresses; mailinglist.addresses end
|
47
|
+
|
48
|
+
# The mail proxy email address
|
49
|
+
def address; "#{mailinglist.name}@#{domain.name}" end
|
50
|
+
# def address=(addr); addresses[0]=addr end
|
51
|
+
|
52
|
+
# The user's "description"--if the user's a person, this would be
|
53
|
+
# their real name.
|
54
|
+
def description
|
55
|
+
mailinglist.description
|
56
|
+
end
|
57
|
+
|
58
|
+
# Change the user's description. (Actually changes the user's
|
59
|
+
# Mailinglist's description.)
|
60
|
+
def description=(new_description)
|
61
|
+
m=mailinglist
|
62
|
+
m.description = new_description
|
63
|
+
m.save
|
64
|
+
end
|
65
|
+
|
66
|
+
# Please change the salt to something else,
|
67
|
+
# Every application should use a different one
|
68
|
+
@@salt = 'thuchpog10.?guf'
|
69
|
+
cattr_accessor :salt
|
70
|
+
|
71
|
+
# Authenticate a user.
|
72
|
+
#
|
73
|
+
# Example:
|
74
|
+
# @user = User.authenticate('bob', 'bobpass')
|
75
|
+
#
|
76
|
+
def self.authenticate(login, pass)
|
77
|
+
find_first(["login = ? AND password = ?", login, sha1(pass)])
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
validates_uniqueness_of :login, :on => :create, :scope => :domain_id
|
82
|
+
|
83
|
+
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
|
84
|
+
# This really needs to be put *back*, with the proviso that I need to
|
85
|
+
# figure out whether it belongs or not (or whether the mailing list
|
86
|
+
# creation should handle it instead, which is likely the case.)
|
87
|
+
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
|
88
|
+
# validates_each :login, :on => :create do |model, attr, value|
|
89
|
+
# if Mailinglist.find(:first, :conditions => [ "name = ?", value ])
|
90
|
+
# model.errors.add(attr, "Address already exists: #{value}" )
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
|
94
|
+
validates_confirmation_of :password
|
95
|
+
validates_length_of :login, :within => 2..40
|
96
|
+
validates_length_of :password, :within => 5..40
|
97
|
+
validates_presence_of :login, :password, :password_confirmation, :domain,
|
98
|
+
:on => :create
|
99
|
+
|
100
|
+
def after_create
|
101
|
+
ml=Mailinglist.new
|
102
|
+
ml.name=self.login
|
103
|
+
ml.user_id=self.id
|
104
|
+
ml.mailinglist_class=MailinglistClass.find 1
|
105
|
+
if ml.save then
|
106
|
+
self.mailinglist=ml
|
107
|
+
return true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def before_destroy
|
112
|
+
ml=self.mailinglist
|
113
|
+
if ml
|
114
|
+
ml.destroy
|
115
|
+
end
|
116
|
+
self.mailinglist_id = nil
|
117
|
+
end
|
118
|
+
|
119
|
+
protected
|
120
|
+
|
121
|
+
# Apply SHA1 encryption to the supplied password.
|
122
|
+
# We will additionally surround the password with a salt
|
123
|
+
# for additional security.
|
124
|
+
def self.sha1(pass)
|
125
|
+
Digest::SHA1.hexdigest("#{salt}--#{pass}--")
|
126
|
+
end
|
127
|
+
|
128
|
+
before_create :crypt_password
|
129
|
+
|
130
|
+
# Before saving the record to database we will crypt the password
|
131
|
+
# using SHA1.
|
132
|
+
# We never store the actual password in the DB.
|
133
|
+
def crypt_password
|
134
|
+
write_attribute "password", self.class.sha1(password)
|
135
|
+
end
|
136
|
+
|
137
|
+
before_update :crypt_unless_empty
|
138
|
+
|
139
|
+
# If the record is updated we will check if the password is empty.
|
140
|
+
# If its empty we assume that the user didn't want to change his
|
141
|
+
# password and just reset it to the old value.
|
142
|
+
def crypt_unless_empty
|
143
|
+
if password.empty?
|
144
|
+
user = self.class.find(self.id)
|
145
|
+
self.password = user.password
|
146
|
+
else
|
147
|
+
write_attribute "password", self.class.sha1(password)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<%= start_form_tag :action=> "login" %>
|
2
|
+
|
3
|
+
<div title="Account login" id="loginform" class="form">
|
4
|
+
<h3>Please login</h3>
|
5
|
+
|
6
|
+
<% if @flash['notice'] %>
|
7
|
+
<div id="message"><%= @flash['notice'] %></div>
|
8
|
+
<% end %>
|
9
|
+
|
10
|
+
<label for="user_login">Login:</label><br/>
|
11
|
+
<input type="text" name="user_login" id="user_login" size="30" value=""/><br/>
|
12
|
+
|
13
|
+
<label for="user_password">Password:</label><br/>
|
14
|
+
<input type="password" name="user_password" id="user_password" size="30"/>
|
15
|
+
|
16
|
+
<br/>
|
17
|
+
<input type="submit" name="login" value="Login »" class="primary" />
|
18
|
+
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<%= end_form_tag %>
|
22
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<%= start_form_tag :action=> "signup" %>
|
2
|
+
|
3
|
+
<div title="Account signup" id="signupform" class="form">
|
4
|
+
<h3>Signup</h3>
|
5
|
+
<%= error_messages_for 'user' %><br/>
|
6
|
+
|
7
|
+
<label for="user_login">Desired login:</label><br/>
|
8
|
+
<%= text_field "user", "login", :size => 30 %><br/>
|
9
|
+
<label for="user_password">Choose password:</label><br/>
|
10
|
+
<%= password_field "user", "password", :size => 30 %><br/>
|
11
|
+
<label for="user_password_confirmation">Confirm password:</label><br/>
|
12
|
+
<%= password_field "user", "password_confirmation", :size => 30 %><br/>
|
13
|
+
|
14
|
+
<input type="submit" value="Signup »" class="primary" />
|
15
|
+
|
16
|
+
<%= end_form_tag %>
|
17
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
<div class="memo">
|
3
|
+
<h3>Welcome</h3>
|
4
|
+
|
5
|
+
<p>You are now logged into the system...</p>
|
6
|
+
<p>
|
7
|
+
Since you are here it's safe to assume the application never called store_location, otherwise
|
8
|
+
you would have been redirected somewhere else after a successful login.
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<%= link_to "« logout", :action=>"logout"%>
|
12
|
+
|
13
|
+
</div>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<h1>Editing address</h1>
|
2
|
+
|
3
|
+
<%= start_form_tag :action => 'edit', :id => @address %>
|
4
|
+
<%= render :partial => 'form' %>
|
5
|
+
<%= submit_tag 'Save' %>
|
6
|
+
<%= end_form_tag %>
|
7
|
+
<%= button_to 'Destroy', { :action => 'destroy', :id => @address }, :confirm => 'Are you sure you want to destroy this address?' %>
|
8
|
+
|
9
|
+
<%= link_to 'Show', :action => 'show', :id => @address %> |
|
10
|
+
<%= link_to 'Back to list', :action => 'list' %>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<h1>Listing addresses</h1>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<% for column in Address.content_columns %>
|
6
|
+
<th><%= column.human_name %></th>
|
7
|
+
<% end %>
|
8
|
+
</tr>
|
9
|
+
|
10
|
+
<% for address in @addresses %>
|
11
|
+
<tr>
|
12
|
+
<% for column in Address.content_columns %>
|
13
|
+
<td><%=h address.send(column.name) %></td>
|
14
|
+
<% end %>
|
15
|
+
<td><%= link_to 'Show', :action => 'show', :id => address %></td>
|
16
|
+
<td><%= link_to 'Edit', :action => 'edit', :id => address %></td>
|
17
|
+
<td><%= link_to 'Destroy', { :action => 'destroy', :id => address }, :post => true, :confirm => 'Are you sure you want to destroy this address?' %>
|
18
|
+
</tr>
|
19
|
+
<% end %>
|
20
|
+
</table>
|
21
|
+
|
22
|
+
<%= link_to 'Previous page', { :page => @address_pages.current.previous } if @address_pages.current.previous %>
|
23
|
+
<%= link_to 'Next page', { :page => @address_pages.current.next } if @address_pages.current.next %>
|
24
|
+
|
25
|
+
<br />
|
26
|
+
|
27
|
+
<%= link_to 'New address', :action => 'new' %>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<%= start_form_tag :action=> "login" %>
|
2
|
+
|
3
|
+
<div title="Account login" id="loginform" class="form">
|
4
|
+
<h3>Please login</h3>
|
5
|
+
|
6
|
+
<% if @flash['notice'] %>
|
7
|
+
<div id="message"><%= @flash['notice'] %></div>
|
8
|
+
<% end %>
|
9
|
+
|
10
|
+
<label for="user_login">Login:</label><br/>
|
11
|
+
<input type="text" name="user_login" id="user_login" size="30" value=""/><br/>
|
12
|
+
|
13
|
+
<label for="user_password">Password:</label><br/>
|
14
|
+
<input type="password" name="user_password" id="user_password" size="30"/>
|
15
|
+
|
16
|
+
<br/>
|
17
|
+
<input type="submit" name="login" value="Login »" class="primary" />
|
18
|
+
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<%= end_form_tag %>
|
22
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<%= start_form_tag :action=> "signup" %>
|
2
|
+
|
3
|
+
<div title="Account signup" id="signupform" class="form">
|
4
|
+
<h3>Signup</h3>
|
5
|
+
<%= error_messages_for 'domain' %><br/>
|
6
|
+
|
7
|
+
<label for="domain_name">Desired domain name:</label><br/>
|
8
|
+
<%= text_field "domain", "name", :size => 30 %><br/>
|
9
|
+
<label for="domain_password">Choose password:</label><br/>
|
10
|
+
<%= password_field "domain", "password", :size => 30 %><br/>
|
11
|
+
<label for="domain_password_confirmation">Confirm password:</label><br/>
|
12
|
+
<%= password_field "domain", "password_confirmation", :size => 30 %><br/>
|
13
|
+
|
14
|
+
<input type="submit" value="Signup »" class="primary" />
|
15
|
+
|
16
|
+
<%= end_form_tag %>
|
17
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
<div class="memo">
|
3
|
+
<h3>Welcome</h3>
|
4
|
+
|
5
|
+
<p>You are now logged into the system...</p>
|
6
|
+
<p>
|
7
|
+
Since you are here it's safe to assume the application never called store_location, otherwise
|
8
|
+
you would have been redirected somewhere else after a successful login.
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<%= link_to "« logout", :action=>"logout"%>
|
12
|
+
|
13
|
+
</div>
|