Compare commits

...

5 Commits

Author SHA1 Message Date
Arthur POULET 99c1495d3f
Add auto-registration mailinglists 2022-11-24 21:57:57 +01:00
Arthur POULET ea02fe40ca
Rewrite some params 2022-11-24 21:46:58 +01:00
Arthur POULET f9a0648264
Improve files dependencies 2022-11-24 21:03:56 +01:00
Arthur POULET 43accadaf3
Fix the subscribe email 2022-11-24 20:49:07 +01:00
Arthur POULET 87002a9571
Fix some coding style 2022-11-24 20:47:51 +01:00
12 changed files with 83 additions and 56 deletions

View File

@ -3,6 +3,8 @@
$LOAD_PATH << File.join(Dir.pwd, "lib") $LOAD_PATH << File.join(Dir.pwd, "lib")
require "app" require "app"
require "protocols"
require "models"
require "pry" require "pry"
# mailinglist1 = Mailinglist.build(name: "FreeML", suffix: "free", strategy: "free").save # mailinglist1 = Mailinglist.build(name: "FreeML", suffix: "free", strategy: "free").save

View File

@ -5,6 +5,8 @@ $LOAD_PATH << File.join(Dir.pwd, "lib")
require "app" require "app"
require "optparse" require "optparse"
require "uuid" require "uuid"
require "protocols"
require "models"
options = { options = {
name: UUID.generate, name: UUID.generate,
@ -21,8 +23,17 @@ OptionParser.new do |opts|
opts.on("-n=NAME", "--name=NAME", "Define the name of the option") do |v| opts.on("-n=NAME", "--name=NAME", "Define the name of the option") do |v|
options[:name] = v options[:name] = v
end end
opts.on("-e=EMAIL", "--email=EMAIL", "Initialize the list with some emails, separated with ,") do |email|
options[:emails] ||= []
options[:emails] << email
end
end.parse! end.parse!
options[:suffix] = options[:name].gsub(/[^a-zA-Z0-9]+/, '-') options[:suffix] = options[:name].gsub(/[^a-zA-Z0-9]+/, '-')
mailinglist = Mailinglist.build(name: options[:name], suffix: options[:suffix], strategy: options[:strategy]).save mailinglist = Mailinglist.build(name: options[:name], suffix: options[:suffix], strategy: options[:strategy]).save
pp mailinglist pp mailinglist
options[:emails].each do |email|
pp Email.register!(name: email, email: email, mailinglist: mailinglist)
end

View File

@ -3,12 +3,12 @@
$LOAD_PATH << File.join(Dir.pwd, "lib") $LOAD_PATH << File.join(Dir.pwd, "lib")
require "app" require "app"
require "protocols"
require "models"
mailinglist0 = Mailinglist.build(name: "AutoReg", suffix: "autoreg", strategy: "autoregister").save
mailinglist1 = Mailinglist.build(name: "FreeML", suffix: "free", strategy: "free").save mailinglist1 = Mailinglist.build(name: "FreeML", suffix: "free", strategy: "free").save
mailinglist2 = Mailinglist.build(name: "ValidatedML", suffix: "validated", strategy: "validated").save mailinglist2 = Mailinglist.build(name: "ValidatedML", suffix: "validated", strategy: "validated").save
mailinglist3 = Mailinglist.build(name: "ClosedML", suffix: "closed", strategy: "closed").save mailinglist3 = Mailinglist.build(name: "ClosedML", suffix: "closed", strategy: "closed").save
pp mailinglist1, mailinglist2, mailinglist3
# email1 = Email.register!(name: "AP", email: "arthur.poulet.hunk@sceptique.eu", mailinglist: mailinglist) pp mailinglist0, mailinglist1, mailinglist2, mailinglist3
# email2 = Email.register!(name: "AP2", email: "arthur.poulet.hunk2@sceptique.eu", mailinglist: mailinglist)
# pp email1, email2

View File

@ -2,6 +2,8 @@
$LOAD_PATH << File.join(Dir.pwd, "lib") $LOAD_PATH << File.join(Dir.pwd, "lib")
require "app" require "app"
require "protocols"
require "models"
require "distributor" require "distributor"
Signal.trap("SIGINT") do Signal.trap("SIGINT") do

View File

@ -2,7 +2,10 @@
$LOAD_PATH << File.join(Dir.pwd, "lib") $LOAD_PATH << File.join(Dir.pwd, "lib")
require "app" require "app"
require "protocols"
require "models"
require "sinatra" require "sinatra"
set :views, File.expand_path(File.join(settings.root + "/../lib/web/views")) set :views, File.expand_path(File.join(settings.root + "/../lib/web/views"))
require "web" require "web"

View File

@ -8,5 +8,3 @@ require "sequel"
$db = Sequel.connect(ENV["DB_URL"]) $db = Sequel.connect(ENV["DB_URL"])
require_relative "logger" require_relative "logger"
require_relative "protocols"
require_relative "models" rescue nil

View File

@ -36,12 +36,13 @@ class Distributor
$logger.info "incoming email from #{mail.from} | #{mail.subject}" $logger.info "incoming email from #{mail.from} | #{mail.subject}"
list = Mailinglist.search_mail(mail) list = Mailinglist.search_mail(mail)
if list if list
# TODO: create list from mail ?
subject, subject_attributes = mail.subject.split(",", 2) subject, subject_attributes = mail.subject.split(",", 2)
attributes = Attributes.parse(subject: subject_attributes, body: mail.body) attributes = Attributes.parse(subject: subject_attributes, body: mail.body)
handler = @handlers[subject] || @handlers[:default] handler = @handlers[subject] || @handlers[:default]
$logger.info "#{handler.class}#handle on #{list.email} for #{mail.from}" $logger.info "#{handler.class}#handle on #{list.email} for #{mail.from}"
handler.handle(list:, to: mail, attributes:) handler.handle(list: list, mail: mail, attributes: attributes)
else
$logger.warn "list #{mail.to} do not exist (asked by #{mail.from})"
end end
mail.seen!(imap_client: @imap_client) mail.seen!(imap_client: @imap_client)
end end

View File

@ -4,31 +4,31 @@ class Distributor
class SetPermissions < Action class SetPermissions < Action
SET_PERMISSIONS_TEMPLATE = Actions.template("set_permissions.success") SET_PERMISSIONS_TEMPLATE = Actions.template("set_permissions.success")
def handle(list:, to:, attributes:) def handle(list:, mail:, attributes:)
return if attributes["user-email"].nil? # drop missing param return if attributes["user-email"].nil? # drop missing param
return if attributes["permissions"].nil? # drop missing param return if attributes["permissions"].nil? # drop missing param
modo = Email.first(mailinglist: list, email: to.from) modo = Email.first(mailinglist: list, email: mail.from)
if !modo&.modo? && !modo&.op? if !modo&.modo? && !modo&.op?
$logger.warn "SECU <#{to.from}> failed to set-permissions <#{list.email}> modo" $logger.warn "SECU <#{mail.from}> failed to set-permissions <#{list.email}> modo"
return nil return nil
end end
user_email = attributes["user-email"] user_email = attributes["user-email"]
user = Email.first(mailinglist: list, email: user_email) user = Email.first(mailinglist: list, email: user_email)
if user.nil? if user.nil?
$logger.warn "SECU <#{to.from}> failed to set-permissions on non-existing email <#{user_email}>" $logger.warn "SECU <#{mail.from}> failed to set-permissions on non-existing email <#{user_email}>"
return nil return nil
end end
if user.op? && !modo.op? if user.op? && !modo.op?
$logger.warn "SECU <#{to.from}> failed to set-permissions on op email <#{user_email}>" $logger.warn "SECU <#{mail.from}> failed to set-permissions on op email <#{user_email}>"
return nil return nil
end end
permissions = attributes["permissions"].to_i permissions = attributes["permissions"].to_i
if Email::Permissions.op?(permissions) && !modo.op? if Email::Permissions.op?(permissions) && !modo.op?
$logger.warn "SECU <#{to.from}> failed to set op permissions on email <#{user_email}>" $logger.warn "SECU <#{mail.from}> failed to set op permissions on email <#{user_email}>"
return nil return nil
end end

View File

@ -7,81 +7,86 @@ class Distributor
WAIT_USER_TEMPLATE = Actions.template("subscribe.wait_user") WAIT_USER_TEMPLATE = Actions.template("subscribe.wait_user")
WAIT_MODO_TEMPLATE = Actions.template("subscribe.wait_modo") WAIT_MODO_TEMPLATE = Actions.template("subscribe.wait_modo")
def handle(list:, to:, attributes:) def handle(list:, mail:, attributes:)
register = register =
begin begin
Email.register!(mailinglist: list, name: to.from_name, email: to.from).save Email.register!(mailinglist: list, name: mail.from_name, email: mail.from).save
rescue StandardError => e rescue StandardError => e
$logger.error e.message $logger.error e.message
nil nil
end end
if register if register
if !register.reader? if !register.reader?
handle_wait_validation(list:, to:, register:) handle_wait_validation(list: list, mail: mail, register: register)
else else
handle_subscribed(list:, to:, register:) handle_subscribed(list: list, mail: mail, register: register)
end end
else else
handle_403(list:, to:) handle_403(list: list, mail: mail)
end end
end end
def handle_wait_validation(list:, to:, register:) def handle_wait_validation(list:, mail:, register:)
$logger.debug "Subscribe#handle_wait_validation on #{list.email} for #{to.from}" $logger.debug "Subscribe#handle_wait_validation on #{list.email} for #{to.from}"
body = WAIT_USER_TEMPLATE.result binding body = WAIT_USER_TEMPLATE.result binding
@distributor.distribute(to.to_response(list:, to:, body:)) @distributor.distribute(mail.to_response(list: list, mail: mail, body: body))
modo = list.enabled_modos.first modo = list.enabled_modos.first
body = WAIT_MODO_TEMPLATE.result binding body = WAIT_MODO_TEMPLATE.result binding
@distributor.distribute( @distributor.distribute(
Protocols::Mail.build( Protocols::Mail.build(
subject: "ML #{list.name} requires validaton for #{to.from}", list:, to: modo.email, body:, subject: "ML #{list.name} requires validaton for #{mail.from}", list: list, to: modo.email, body: body,
), ),
) )
end end
def handle_subscribed(list:, to:, register:) def handle_subscribed(list:, mail:, register:)
$logger.debug "Subscribe#handle_subscribed on #{list.email} for #{to.from}" $logger.debug "Subscribe#handle_subscribed on #{list.email} for #{mail.from}"
$logger.debug register.inspect $logger.debug register.inspect
body = SUCCESS_TEMPLATE.result binding body = SUCCESS_TEMPLATE.result binding
@distributor.distribute(to.to_response(list:, to:, body:)) @distributor.distribute(mail.to_response(list: list, mail: mail, body: body))
end end
def handle_403(list:, to:) def handle_403(list:, mail:)
$logger.debug "Subscribe#handle_403 on #{list.email} for #{to.from}" $logger.debug "Subscribe#handle_403 on #{list.email} for #{mail.from}"
body = FORBIDDEN_TEMPLATE.result binding body = FORBIDDEN_TEMPLATE.result binding
@distributor.distribute(to.to_response(list:, to:, body:)) @distributor.distribute(mail.to_response(list: list, mail: mail, body: body))
end end
end end
class Unsubscribe < Action class Unsubscribe < Action
SUCCESS_TEMPLATE = Actions.template("unsubscribe.success") SUCCESS_TEMPLATE = Actions.template("unsubscribe.success")
def handle(list:, to:, attributes:) def handle(list:, mail:, attributes:)
Email.unregister!(mailinglist: list, email: to.from) Email.unregister!(mailinglist: list, email: mail.from)
body = SUCCESS_TEMPLATE.result binding body = SUCCESS_TEMPLATE.result binding
@distributor.distribute(to.to_response(list:, to:, body:)) @distributor.distribute(mail.to_response(list: list, mail: mail, body: body))
end end
end end
class Help < Action class Help < Action
HELP_TEMPLATE = Actions.template("help") HELP_TEMPLATE = Actions.template("help")
def handle(list:, to:, attributes:)
def handle(list:, mail:, attributes:)
body = HELP_TEMPLATE.result binding body = HELP_TEMPLATE.result binding
@distributor.distribute(to.to_response(list:, to:, body:)) @distributor.distribute(mail.to_response(list: list, mail: mail, body: body))
end end
end end
# This distribute the mail among the readers # This distribute the mail among the readers
class Distribute < Action class Distribute < Action
def handle(list:, to:, attributes:) def handle(list:, mail:, attributes:)
if !list if !list.enabled_writers.find { _1.email == mail.from }
$logger.warn "invalid email writer for #{mail.from} on #{mail.to}" if list.registration?("autoregister")
return nil Email.register!(mailinglist: list, name: mail.from_name, email: mail.from).save
else
$logger.warn "invalid email writer for #{mail.from} on #{mail.to}"
return nil
end
end end
list.enabled_readers.each do |reader| list.enabled_readers.each do |reader|
to_distrib = to.to_redistribute(list:, dest: reader) to_distrib = mail.to_redistribute(list: list, dest: reader)
@distributor.distribute(to_distrib) @distributor.distribute(to_distrib)
end end
end end

View File

@ -1,3 +1,3 @@
You have subscribed to <%= list.name %> <<%= list.email %>> You have subscribed to <%= list.name %> <<%= list.email %>>
Your current permissions are <%= register.permissions.to_s(2).rjust(8, "0") %> Your current permissions are <%= register.permissions_words.join(', ') %>

View File

@ -6,8 +6,8 @@ class Mailinglist < Sequel::Model($db)
HOST = ENV["MAILINGLIST_HOST"] HOST = ENV["MAILINGLIST_HOST"]
STRATEGIES = { STRATEGIES = {
registration: %w[free validated closed], registration: %w[autoregister free validated closed],
moderate: %w[freewrite restrictedwrite], moderation: %w[freewrite restrictedwrite],
}.freeze }.freeze
one_to_many :emails one_to_many :emails
@ -23,7 +23,7 @@ class Mailinglist < Sequel::Model($db)
end end
def self.build(name:, aliasing: nil, suffix: nil, strategy: "closed") def self.build(name:, aliasing: nil, suffix: nil, strategy: "closed")
email = "#{BASE_USER}" email = BASE_USER.dup
email << SUFFIX_SEPARATOR << suffix if suffix email << SUFFIX_SEPARATOR << suffix if suffix
aliasing = UUID.generate if !suffix && !aliasing aliasing = UUID.generate if !suffix && !aliasing
email << aliasing if aliasing email << aliasing if aliasing
@ -48,27 +48,35 @@ class Mailinglist < Sequel::Model($db)
end end
def registration def registration
strategy.split("|").select { STRATEGIES[:registration].include(_1) } strategy.split("|").filter { STRATEGIES[:registration].include?(_1) }
end
def registration?(find)
registration.include?(find)
end end
def moderation def moderation
strategy.split("|").select { STRATEGIES[:registration].include(_1) } strategy.split("|").filter { STRATEGIES[:moderation].include?(_1) }
end
def moderation?(find)
moderation.include?(find)
end end
def enabled_readers def enabled_readers
emails.filter{ _1.permissions & Email::Permissions::READ != 0 } emails.filter { _1.permissions & Email::Permissions::READ != 0 }
end end
def enabled_writers def enabled_writers
emails.filter{ _1.permissions & Email::Permissions::WRITE != 0 } emails.filter { _1.permissions & Email::Permissions::WRITE != 0 }
end end
def enabled_admins def enabled_admins
emails.filter{ _1.permissions & Email::Permissions::ADMIN != 0 } emails.filter { _1.permissions & Email::Permissions::ADMIN != 0 }
end end
def enabled_modos def enabled_modos
emails.filter{ _1.permissions & Email::Permissions::MODO != 0 } emails.filter { _1.permissions & Email::Permissions::MODO != 0 }
end end
def actions_emails def actions_emails
@ -100,9 +108,7 @@ class Mailinglist < Sequel::Model($db)
end end
def set_permissions_email(*permissions_symbols, user_email: nil, permissions: nil) def set_permissions_email(*permissions_symbols, user_email: nil, permissions: nil)
if permissions.nil? permissions = Email::Permissions.from_symbols(*permissions_symbols) if permissions.nil?
permissions = Email::Permissions.from_symbols(*permissions_symbols)
end
user_part = user_email ? ",user-email=#{user_email}" : "" user_part = user_email ? ",user-email=#{user_email}" : ""
permissions_part = ",permissions=#{permissions}" permissions_part = ",permissions=#{permissions}"
"#{email}?subject=set-permissions#{user_part}#{permissions_part}" "#{email}?subject=set-permissions#{user_part}#{permissions_part}"
@ -112,5 +118,4 @@ class Mailinglist < Sequel::Model($db)
"#{email}?subject=list-users" "#{email}?subject=list-users"
end end
end end

View File

@ -78,7 +78,7 @@ class Protocols::Mail
["List-Post", "<mailto:#{list.post_email}>"], ["List-Post", "<mailto:#{list.post_email}>"],
["List-Owner", "<mailto:#{list.owner_email}>"], ["List-Owner", "<mailto:#{list.owner_email}>"],
["In-Reply-To", @message_id], ["In-Reply-To", @message_id],
["Precedence", "list"], %w[Precedence list],
# ["Precedence", "bulk"], # ["Precedence", "bulk"],
["Message-Id", "<#{UUID.generate}@#{ENV['SENDER_HOST']}>"], ["Message-Id", "<#{UUID.generate}@#{ENV['SENDER_HOST']}>"],
["X-Sequence", (@seq + 1).to_s], ["X-Sequence", (@seq + 1).to_s],
@ -124,8 +124,8 @@ class Protocols::Mail
# Create a response email for an existing one. # Create a response email for an existing one.
# #
# @param list [Mailinglist] # @param list [Mailinglist]
# @param to [Protocols::Mail] # @param mail [Protocols::Mail]
def to_response(list:, to:, body:) def to_response(list:, mail:, body:)
new = clone new = clone
new.replace_headers!( new.replace_headers!(
["User-Agent", USER_AGENT], ["User-Agent", USER_AGENT],
@ -139,7 +139,7 @@ class Protocols::Mail
["Message-Id", "<#{UUID.generate}@#{ENV['SENDER_HOST']}>"], ["Message-Id", "<#{UUID.generate}@#{ENV['SENDER_HOST']}>"],
) )
new.to = to.from new.to = mail.from
new.from = list.email new.from = list.email
new.body = "#{body}#{list.signature}" new.body = "#{body}#{list.signature}"
new new