1
0
مراية لـ https://github.com/postalserver/postal.git تم المزامنة 2026-01-17 21:39:47 +00:00

style(rubocop): fix all safe auto correctable offenses

هذا الالتزام موجود في:
Charlie Smurthwaite
2023-03-16 15:50:53 +00:00
الأصل 02c93a4850
التزام fd289c46fd
204 ملفات معدلة مع 2611 إضافات و2486 حذوفات

عرض الملف

@@ -13,22 +13,21 @@
class AdditionalRouteEndpoint < ApplicationRecord
belongs_to :route
belongs_to :endpoint, :polymorphic => true
belongs_to :endpoint, polymorphic: true
validate :validate_endpoint_belongs_to_server
validate :validate_wildcard
validate :validate_uniqueness
def self.find_by_endpoint(endpoint)
class_name, id = endpoint.split('#', 2)
class_name, id = endpoint.split("#", 2)
unless Route::ENDPOINT_TYPES.include?(class_name)
raise Postal::Error, "Invalid endpoint class name '#{class_name}'"
end
if uuid = class_name.constantize.find_by_uuid(id)
where(:endpoint_type => class_name, :endpoint_id => uuid).first
else
nil
end
return unless uuid = class_name.constantize.find_by_uuid(id)
where(endpoint_type: class_name, endpoint_id: uuid).first
end
def _endpoint
@@ -38,39 +37,37 @@ class AdditionalRouteEndpoint < ApplicationRecord
def _endpoint=(value)
if value.blank?
self.endpoint = nil
else
if value =~ /\#/
class_name, id = value.split('#', 2)
unless Route::ENDPOINT_TYPES.include?(class_name)
raise Postal::Error, "Invalid endpoint class name '#{class_name}'"
end
self.endpoint = class_name.constantize.find_by_uuid(id)
else
self.endpoint = nil
elsif value =~ /\#/
class_name, id = value.split("#", 2)
unless Route::ENDPOINT_TYPES.include?(class_name)
raise Postal::Error, "Invalid endpoint class name '#{class_name}'"
end
self.endpoint = class_name.constantize.find_by_uuid(id)
else
self.endpoint = nil
end
end
private
def validate_endpoint_belongs_to_server
if self.endpoint && self.endpoint&.server != self.route.server
errors.add :endpoint, :invalid
end
return unless endpoint && endpoint&.server != route.server
errors.add :endpoint, :invalid
end
def validate_uniqueness
if self.endpoint == self.route.endpoint
errors.add :base, "You can only add an endpoint to a route once"
end
return unless endpoint == route.endpoint
errors.add :base, "You can only add an endpoint to a route once"
end
def validate_wildcard
if self.route.wildcard?
if self.endpoint_type == 'SMTPEndpoint' || self.endpoint_type == 'AddressEndpoint'
errors.add :base, "SMTP or address endpoints are not permitted on wildcard routes"
end
end
return unless route.wildcard?
return unless endpoint_type == "SMTPEndpoint" || endpoint_type == "AddressEndpoint"
errors.add :base, "SMTP or address endpoints are not permitted on wildcard routes"
end
end

عرض الملف

@@ -16,10 +16,10 @@ class AddressEndpoint < ApplicationRecord
include HasUUID
belongs_to :server
has_many :routes, :as => :endpoint
has_many :additional_route_endpoints, :dependent => :destroy, :as => :endpoint
has_many :routes, as: :endpoint
has_many :additional_route_endpoints, dependent: :destroy, as: :endpoint
validates :address, :presence => true, :format => {:with => /@/}, :uniqueness => {:scope => [:server_id], :message => "has already been added"}
validates :address, presence: true, format: { with: /@/ }, uniqueness: { scope: [:server_id], message: "has already been added" }
before_destroy :update_routes
@@ -28,15 +28,15 @@ class AddressEndpoint < ApplicationRecord
end
def update_routes
self.routes.each { |r| r.update(:endpoint => nil, :mode => 'Reject') }
routes.each { |r| r.update(endpoint: nil, mode: "Reject") }
end
def description
self.address
address
end
def domain
address.split('@', 2).last
address.split("@", 2).last
end
end

عرض الملف

@@ -1,5 +1,7 @@
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
self.inheritance_column = 'sti_type'
self.inheritance_column = "sti_type"
nilify_blanks
end

عرض الملف

@@ -5,7 +5,7 @@ module HasMessage
end
def message
@message ||= self.server.message_db.message(self.message_id)
@message ||= server.message_db.message(message_id)
end
def message=(message)
@@ -14,6 +14,7 @@ module HasMessage
end
module ClassMethods
def include_message
queued_messages = all.to_a
server_ids = queued_messages.map(&:server_id).uniq
@@ -22,10 +23,11 @@ module HasMessage
elsif server_ids.size > 1
raise Postal::Error, "'include_message' can only be used on collections of messages from the same server"
end
message_ids = queued_messages.map(&:message_id).uniq
server = queued_messages.first&.server
messages = server.message_db.messages(:where => {:id => message_ids}).each_with_object({}) do |message, hash|
hash[message.id] = message
messages = server.message_db.messages(where: { id: message_ids }).index_by do |message|
message.id
end
queued_messages.each do |queued_message|
if m = messages[queued_message.message_id]
@@ -33,6 +35,7 @@ module HasMessage
end
end
end
end
end

عرض الملف

@@ -3,16 +3,16 @@ module HasSoftDestroy
def self.included(base)
base.define_callbacks :soft_destroy
base.class_eval do
scope :deleted, -> { where.not(:deleted_at => nil) }
scope :present, -> { where(:deleted_at => nil) }
scope :deleted, -> { where.not(deleted_at: nil) }
scope :present, -> { where(deleted_at: nil) }
end
end
def soft_destroy
run_callbacks :soft_destroy do
self.deleted_at = Time.now
self.save!
ActionDeletionJob.queue(:main, :type => self.class.name, :id => self.id)
save!
ActionDeletionJob.queue(:main, type: self.class.name, id: id)
end
end

عرض الملف

@@ -1,11 +1,13 @@
module HasUUID
def self.included(base)
base.class_eval do
random_string :uuid, :type => :uuid, :unique => true
random_string :uuid, type: :uuid, unique: true
end
end
def to_param
uuid
end
end

عرض الملف

@@ -21,11 +21,11 @@ class Credential < ApplicationRecord
belongs_to :server
TYPES = ['SMTP', 'API', 'SMTP-IP']
TYPES = ["SMTP", "API", "SMTP-IP"]
validates :key, :presence => true, :uniqueness => true
validates :type, :inclusion => {:in => TYPES}
validates :name, :presence => true
validates :key, presence: true, uniqueness: true
validates :type, inclusion: { in: TYPES }
validates :name, presence: true
validate :validate_key_cannot_be_changed
validate :validate_key_for_smtp_ip
@@ -33,10 +33,9 @@ class Credential < ApplicationRecord
before_validation :generate_key
def generate_key
return if self.type == 'SMTP-IP'
return if self.persisted?
return if type == "SMTP-IP"
return if persisted?
self.key = SecureRandomString.new(24)
end
@@ -51,26 +50,26 @@ class Credential < ApplicationRecord
def usage_type
if last_used_at.nil?
'Unused'
"Unused"
elsif last_used_at < 1.year.ago
'Inactive'
"Inactive"
elsif last_used_at < 6.months.ago
'Dormant'
"Dormant"
elsif last_used_at < 1.month.ago
'Quiet'
"Quiet"
else
'Active'
"Active"
end
end
def to_smtp_plain
Base64.encode64("\0XX\0#{self.key}").strip
Base64.encode64("\0XX\0#{key}").strip
end
def ipaddr
return unless type == 'SMTP-IP'
return unless type == "SMTP-IP"
@ipaddr ||= IPAddr.new(self.key)
@ipaddr ||= IPAddr.new(key)
rescue IPAddr::InvalidAddressError
nil
end
@@ -80,15 +79,15 @@ class Credential < ApplicationRecord
def validate_key_cannot_be_changed
return if new_record?
return unless key_changed?
return if type == 'SMTP-IP'
return if type == "SMTP-IP"
errors.add :key, "cannot be changed"
end
def validate_key_for_smtp_ip
return unless type == 'SMTP-IP'
return unless type == "SMTP-IP"
IPAddr.new(self.key.to_s)
IPAddr.new(key.to_s)
rescue IPAddr::InvalidAddressError
errors.add :key, "must be a valid IPv4 or IPv6 address"
end

عرض الملف

@@ -34,39 +34,39 @@
# index_domains_on_uuid (uuid)
#
require 'resolv'
require "resolv"
class Domain < ApplicationRecord
include HasUUID
require_dependency 'domain/dns_checks'
require_dependency 'domain/dns_verification'
require_dependency "domain/dns_checks"
require_dependency "domain/dns_verification"
VERIFICATION_EMAIL_ALIASES = ['webmaster', 'postmaster', 'admin', 'administrator', 'hostmaster']
VERIFICATION_EMAIL_ALIASES = ["webmaster", "postmaster", "admin", "administrator", "hostmaster"]
belongs_to :server, :optional => true
belongs_to :owner, :optional => true, :polymorphic => true
has_many :routes, :dependent => :destroy
has_many :track_domains, :dependent => :destroy
belongs_to :server, optional: true
belongs_to :owner, optional: true, polymorphic: true
has_many :routes, dependent: :destroy
has_many :track_domains, dependent: :destroy
VERIFICATION_METHODS = ['DNS', 'Email']
VERIFICATION_METHODS = ["DNS", "Email"]
validates :name, :presence => true, :format => {:with => /\A[a-z0-9\-\.]*\z/}, :uniqueness => {:scope => [:owner_type, :owner_id], :message => "is already added"}
validates :verification_method, :inclusion => {:in => VERIFICATION_METHODS}
validates :name, presence: true, format: { with: /\A[a-z0-9\-.]*\z/ }, uniqueness: { scope: [:owner_type, :owner_id], message: "is already added" }
validates :verification_method, inclusion: { in: VERIFICATION_METHODS }
random_string :dkim_identifier_string, :type => :chars, :length => 6, :unique => true, :upper_letters_only => true
random_string :dkim_identifier_string, type: :chars, length: 6, unique: true, upper_letters_only: true
before_create :generate_dkim_key
scope :verified, -> { where.not(:verified_at => nil) }
scope :verified, -> { where.not(verified_at: nil) }
when_attribute :verification_method, :changes_to => :anything do
when_attribute :verification_method, changes_to: :anything do
before_save do
if self.verification_method == 'DNS'
self.verification_token = Nifty::Utils::RandomString.generate(:length => 32)
elsif self.verification_method == 'Email'
self.verification_token = rand(999999).to_s.ljust(6, '0')
if verification_method == "DNS"
self.verification_token = Nifty::Utils::RandomString.generate(length: 32)
elsif verification_method == "Email"
self.verification_token = rand(999_999).to_s.ljust(6, "0")
else
self.verification_token = nil
end
@@ -79,13 +79,13 @@ class Domain < ApplicationRecord
def verify
self.verified_at = Time.now
self.save!
save!
end
def parent_domains
parts = self.name.split('.')
parts[0,parts.size-1].each_with_index.map do |p, i|
parts[i..-1].join('.')
parts = name.split(".")
parts[0, parts.size - 1].each_with_index.map do |p, i|
parts[i..-1].join(".")
end
end
@@ -94,7 +94,7 @@ class Domain < ApplicationRecord
end
def dkim_key
@dkim_key ||= OpenSSL::PKey::RSA.new(self.dkim_private_key)
@dkim_key ||= OpenSSL::PKey::RSA.new(dkim_private_key)
end
def to_param
@@ -114,12 +114,12 @@ class Domain < ApplicationRecord
end
def dkim_record
public_key = dkim_key.public_key.to_s.gsub(/\-+[A-Z ]+\-+\n/, '').gsub(/\n/, '')
public_key = dkim_key.public_key.to_s.gsub(/-+[A-Z ]+-+\n/, "").gsub(/\n/, "")
"v=DKIM1; t=s; h=sha256; p=#{public_key};"
end
def dkim_identifier
Postal.config.dns.dkim_identifier + "-#{self.dkim_identifier_string}"
Postal.config.dns.dkim_identifier + "-#{dkim_identifier_string}"
end
def dkim_record_name
@@ -127,7 +127,7 @@ class Domain < ApplicationRecord
end
def return_path_domain
"#{Postal.config.dns.custom_return_path_prefix}.#{self.name}"
"#{Postal.config.dns.custom_return_path_prefix}.#{name}"
end
def nameservers
@@ -135,7 +135,7 @@ class Domain < ApplicationRecord
end
def resolver
@resolver ||= Postal.config.general.use_local_ns_for_domains? ? Resolv::DNS.new : Resolv::DNS.new(:nameserver => nameservers)
@resolver ||= Postal.config.general.use_local_ns_for_domains? ? Resolv::DNS.new : Resolv::DNS.new(nameserver: nameservers)
end
private
@@ -143,15 +143,17 @@ class Domain < ApplicationRecord
def get_nameservers
local_resolver = Resolv::DNS.new
ns_records = []
parts = name.split('.')
parts = name.split(".")
(parts.size - 1).times do |n|
d = parts[n, parts.size - n + 1].join('.')
d = parts[n, parts.size - n + 1].join(".")
ns_records = local_resolver.getresources(d, Resolv::DNS::Resource::IN::NS).map { |s| s.name.to_s }
break unless ns_records.blank?
break if ns_records.present?
end
return [] if ns_records.blank?
ns_records = ns_records.map{|r| local_resolver.getresources(r, Resolv::DNS::Resource::IN::A).map { |s| s.address.to_s} }.flatten
ns_records = ns_records.map { |r| local_resolver.getresources(r, Resolv::DNS::Resource::IN::A).map { |s| s.address.to_s } }.flatten
return [] if ns_records.blank?
ns_records
end

عرض الملف

@@ -1,9 +1,9 @@
require 'resolv'
require "resolv"
class Domain
def dns_ok?
spf_status == 'OK' && dkim_status == 'OK' && ['OK', 'Missing'].include?(self.mx_status) && ['OK', 'Missing'].include?(self.return_path_status)
spf_status == "OK" && dkim_status == "OK" && ["OK", "Missing"].include?(mx_status) && ["OK", "Missing"].include?(return_path_status)
end
def dns_checked?
@@ -16,21 +16,21 @@ class Domain
check_mx_records
check_return_path_record
self.dns_checked_at = Time.now
self.save!
if source == :auto && !dns_ok? && self.owner.is_a?(Server)
WebhookRequest.trigger(self.owner, 'DomainDNSError', {
:server => self.owner.webhook_hash,
:domain => self.name,
:uuid => self.uuid,
:dns_checked_at => self.dns_checked_at.to_f,
:spf_status => self.spf_status,
:spf_error => self.spf_error,
:dkim_status => self.dkim_status,
:dkim_error => self.dkim_error,
:mx_status => self.mx_status,
:mx_error => self.mx_error,
:return_path_status => self.return_path_status,
:return_path_error => self.return_path_error
save!
if source == :auto && !dns_ok? && owner.is_a?(Server)
WebhookRequest.trigger(owner, "DomainDNSError", {
server: owner.webhook_hash,
domain: name,
uuid: uuid,
dns_checked_at: dns_checked_at.to_f,
spf_status: spf_status,
spf_error: spf_error,
dkim_status: dkim_status,
dkim_error: dkim_error,
mx_status: mx_status,
mx_error: mx_error,
return_path_status: return_path_status,
return_path_error: return_path_error
})
end
dns_ok?
@@ -41,19 +41,19 @@ class Domain
#
def check_spf_record
result = resolver.getresources(self.name, Resolv::DNS::Resource::IN::TXT)
spf_records = result.map(&:data).select { |d| d =~ /\Av=spf1/}
result = resolver.getresources(name, Resolv::DNS::Resource::IN::TXT)
spf_records = result.map(&:data).select { |d| d =~ /\Av=spf1/ }
if spf_records.empty?
self.spf_status = 'Missing'
self.spf_error = 'No SPF record exists for this domain'
self.spf_status = "Missing"
self.spf_error = "No SPF record exists for this domain"
else
suitable_spf_records = spf_records.select { |d| d =~ /include\:\s*#{Regexp.escape(Postal.config.dns.spf_include)}/}
suitable_spf_records = spf_records.select { |d| d =~ /include:\s*#{Regexp.escape(Postal.config.dns.spf_include)}/ }
if suitable_spf_records.empty?
self.spf_status = 'Invalid'
self.spf_status = "Invalid"
self.spf_error = "An SPF record exists but it doesn't include #{Postal.config.dns.spf_include}"
false
else
self.spf_status = 'OK'
self.spf_status = "OK"
self.spf_error = nil
true
end
@@ -68,24 +68,24 @@ class Domain
#
# DKIM
#
def check_dkim_record
domain = "#{dkim_record_name}.#{name}"
result = resolver.getresources(domain, Resolv::DNS::Resource::IN::TXT)
records = result.map(&:data)
if records.empty?
self.dkim_status = 'Missing'
self.dkim_status = "Missing"
self.dkim_error = "No TXT records were returned for #{domain}"
else
sanitised_dkim_record = records.first.strip.ends_with?(';') ? records.first.strip : "#{records.first.strip};"
sanitised_dkim_record = records.first.strip.ends_with?(";") ? records.first.strip : "#{records.first.strip};"
if records.size > 1
self.dkim_status = 'Invalid'
self.dkim_status = "Invalid"
self.dkim_error = "There are #{records.size} records for at #{domain}. There should only be one."
elsif sanitised_dkim_record != self.dkim_record
self.dkim_status = 'Invalid'
elsif sanitised_dkim_record != dkim_record
self.dkim_status = "Invalid"
self.dkim_error = "The DKIM record at #{domain} does not match the record we have provided. Please check it has been copied correctly."
else
self.dkim_status = 'OK'
self.dkim_status = "OK"
self.dkim_error = nil
true
end
@@ -102,21 +102,21 @@ class Domain
#
def check_mx_records
result = resolver.getresources(self.name, Resolv::DNS::Resource::IN::MX)
result = resolver.getresources(name, Resolv::DNS::Resource::IN::MX)
records = result.map(&:exchange)
if records.empty?
self.mx_status = 'Missing'
self.mx_error = "There are no MX records for #{self.name}"
self.mx_status = "Missing"
self.mx_error = "There are no MX records for #{name}"
else
missing_records = Postal.config.dns.mx_records.dup - records.map { |r| r.to_s.downcase }
if missing_records.empty?
self.mx_status = 'OK'
self.mx_status = "OK"
self.mx_error = nil
elsif missing_records.size == Postal.config.dns.mx_records.size
self.mx_status = 'Missing'
self.mx_error = 'You have MX records but none of them point to us.'
self.mx_status = "Missing"
self.mx_error = "You have MX records but none of them point to us."
else
self.mx_status = 'Invalid'
self.mx_status = "Invalid"
self.mx_error = "MX #{missing_records.size == 1 ? 'record' : 'records'} for #{missing_records.to_sentence} are missing and are required."
end
end
@@ -132,19 +132,17 @@ class Domain
#
def check_return_path_record
result = resolver.getresources(self.return_path_domain, Resolv::DNS::Resource::IN::CNAME)
result = resolver.getresources(return_path_domain, Resolv::DNS::Resource::IN::CNAME)
records = result.map { |r| r.name.to_s.downcase }
if records.empty?
self.return_path_status = 'Missing'
self.return_path_error = "There is no return path record at #{self.return_path_domain}"
self.return_path_status = "Missing"
self.return_path_error = "There is no return path record at #{return_path_domain}"
elsif records.size == 1 && records.first == Postal.config.dns.return_path
self.return_path_status = "OK"
self.return_path_error = nil
else
if records.size == 1 && records.first == Postal.config.dns.return_path
self.return_path_status = 'OK'
self.return_path_error = nil
else
self.return_path_status = 'Invalid'
self.return_path_error = "There is a CNAME record at #{self.return_path_domain} but it points to #{records.first} which is incorrect. It should point to #{Postal.config.dns.return_path}."
end
self.return_path_status = "Invalid"
self.return_path_error = "There is a CNAME record at #{return_path_domain} but it points to #{records.first} which is incorrect. It should point to #{Postal.config.dns.return_path}."
end
end

عرض الملف

@@ -1,4 +1,4 @@
require 'resolv'
require "resolv"
class Domain
@@ -7,11 +7,12 @@ class Domain
end
def verify_with_dns
return false unless self.verification_method == 'DNS'
result = resolver.getresources(self.name, Resolv::DNS::Resource::IN::TXT)
if result.map { |d| d.data.to_s.strip}.include?(self.dns_verification_string)
return false unless verification_method == "DNS"
result = resolver.getresources(name, Resolv::DNS::Resource::IN::TXT)
if result.map { |d| d.data.to_s.strip }.include?(dns_verification_string)
self.verified_at = Time.now
self.save
save
else
false
end

عرض الملف

@@ -26,19 +26,19 @@ class HTTPEndpoint < ApplicationRecord
include HasUUID
belongs_to :server
has_many :routes, :as => :endpoint
has_many :additional_route_endpoints, :dependent => :destroy, :as => :endpoint
has_many :routes, as: :endpoint
has_many :additional_route_endpoints, dependent: :destroy, as: :endpoint
ENCODINGS = ['BodyAsJSON', 'FormData']
FORMATS = ['Hash', 'RawMessage']
ENCODINGS = ["BodyAsJSON", "FormData"]
FORMATS = ["Hash", "RawMessage"]
before_destroy :update_routes
validates :name, :presence => true
validates :url, :presence => true
validates :encoding, :inclusion => {:in => ENCODINGS}
validates :format, :inclusion => {:in => FORMATS}
validates :timeout, :numericality => {:greater_than_or_equal_to => 5, :less_than_or_equal_to => 60}
validates :name, presence: true
validates :url, presence: true
validates :encoding, inclusion: { in: ENCODINGS }
validates :format, inclusion: { in: FORMATS }
validates :timeout, numericality: { greater_than_or_equal_to: 5, less_than_or_equal_to: 60 }
default_value :timeout, -> { DEFAULT_TIMEOUT }
@@ -51,7 +51,7 @@ class HTTPEndpoint < ApplicationRecord
end
def update_routes
self.routes.each { |r| r.update(:endpoint => nil, :mode => 'Reject') }
routes.each { |r| r.update(endpoint: nil, mode: "Reject") }
end
end

عرض الملف

@@ -18,27 +18,23 @@ class IncomingMessagePrototype
end
def from_address
@from.gsub(/.*</, '').gsub(/>.*/, '').strip
@from.gsub(/.*</, "").gsub(/>.*/, "").strip
end
def route
@routes ||= begin
if @to.present?
uname, domain = @to.split('@', 2)
uname, tag = uname.split('+', 2)
@server.routes.includes(:domain).where(:domains => {:name => domain}, :name => uname).first
else
nil
end
end
@routes ||= if @to.present?
uname, domain = @to.split("@", 2)
uname, tag = uname.split("+", 2)
@server.routes.includes(:domain).where(domains: { name: domain }, name: uname).first
end
end
def attachments
(@attachments || []).map do |attachment|
{
:name => attachment[:name],
:content_type => attachment[:content_type] || 'application/octet-stream',
:data => attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data]
name: attachment[:name],
content_type: attachment[:content_type] || "application/octet-stream",
data: attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data]
}
end
end
@@ -47,10 +43,10 @@ class IncomingMessagePrototype
if valid?
messages = route.create_messages do |message|
message.rcpt_to = @to
message.mail_from = self.from_address
message.raw_message = self.raw_message
message.mail_from = from_address
message.raw_message = raw_message
end
{route.description => {:id => messages.first.id, :token => messages.first.token}}
{ route.description => { id: messages.first.id, token: messages.first.token } }
else
false
end
@@ -66,7 +62,7 @@ class IncomingMessagePrototype
end
def validate
@errors = Array.new
@errors = []
if route.nil?
@errors << "NoRoutesFound"
end
@@ -91,11 +87,11 @@ class IncomingMessagePrototype
mail.message_id = "<#{SecureRandom.uuid}@#{Postal.config.dns.return_path}>"
attachments.each do |attachment|
mail.attachments[attachment[:name]] = {
:mime_type => attachment[:content_type],
:content => attachment[:data]
mime_type: attachment[:content_type],
content: attachment[:data]
}
end
mail.header['Received'] = "from #{@source_type} (#{@ip} [#{@ip}]) by Postal with HTTP; #{Time.now.utc.rfc2822.to_s}"
mail.header["Received"] = "from #{@source_type} (#{@ip} [#{@ip}]) by Postal with HTTP; #{Time.now.utc.rfc2822}"
mail.to_s
end
end

عرض الملف

@@ -16,9 +16,9 @@ class IPAddress < ApplicationRecord
belongs_to :ip_pool
validates :ipv4, :presence => true, :uniqueness => true
validates :hostname, :presence => true
validates :ipv6, :uniqueness => {:allow_blank => true}
validates :ipv4, presence: true, uniqueness: true
validates :hostname, presence: true
validates :ipv6, uniqueness: { allow_blank: true }
validates :priority, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100, only_integer: true }
scope :order_by_priority, -> { order(priority: :desc) }

عرض الملف

@@ -18,16 +18,16 @@ class IPPool < ApplicationRecord
include HasUUID
validates :name, :presence => true
validates :name, presence: true
has_many :ip_addresses, :dependent => :restrict_with_exception
has_many :servers, :dependent => :restrict_with_exception
has_many :organization_ip_pools, :dependent => :destroy
has_many :organizations, :through => :organization_ip_pools
has_many :ip_pool_rules, :dependent => :destroy
has_many :ip_addresses, dependent: :restrict_with_exception
has_many :servers, dependent: :restrict_with_exception
has_many :organization_ip_pools, dependent: :destroy
has_many :organizations, through: :organization_ip_pools
has_many :ip_pool_rules, dependent: :destroy
def self.default
where(:default => true).order(:id).first
where(default: true).order(:id).first
end
end

عرض الملف

@@ -17,24 +17,24 @@ class IPPoolRule < ApplicationRecord
include HasUUID
belongs_to :owner, :polymorphic => true
belongs_to :owner, polymorphic: true
belongs_to :ip_pool
validate :validate_from_and_to_addresses
validate :validate_ip_pool_belongs_to_organization
def from
from_text ? from_text.gsub(/\r/, '').split(/\n/).map(&:strip) : []
from_text ? from_text.gsub(/\r/, "").split(/\n/).map(&:strip) : []
end
def to
to_text ? to_text.gsub(/\r/, '').split(/\n/).map(&:strip) : []
to_text ? to_text.gsub(/\r/, "").split(/\n/).map(&:strip) : []
end
def apply_to_message?(message)
if from.present? && message.headers['from'].present?
if from.present? && message.headers["from"].present?
from.each do |condition|
if message.headers['from'].any? { |f| self.class.address_matches?(condition, f) }
if message.headers["from"].any? { |f| self.class.address_matches?(condition, f) }
return true
end
end
@@ -54,28 +54,29 @@ class IPPoolRule < ApplicationRecord
private
def validate_from_and_to_addresses
if self.from.empty? && self.to.empty?
errors.add :base, "At least one rule condition must be specified"
end
return unless from.empty? && to.empty?
errors.add :base, "At least one rule condition must be specified"
end
def validate_ip_pool_belongs_to_organization
org = self.owner.is_a?(Organization) ? self.owner : self.owner.organization
if self.ip_pool && self.ip_pool_id_changed? && !org.ip_pools.include?(self.ip_pool)
errors.add :ip_pool_id, "must belong to the organization"
end
org = owner.is_a?(Organization) ? owner : owner.organization
return unless ip_pool && ip_pool_id_changed? && !org.ip_pools.include?(ip_pool)
errors.add :ip_pool_id, "must belong to the organization"
end
def self.address_matches?(condition, address)
address = Postal::Helpers.strip_name_from_address(address)
if condition =~ /@/
parts = address.split('@')
domain, uname = parts.pop, parts.join('@')
uname, _ = uname.split('+', 2)
parts = address.split("@")
domain = parts.pop
uname = parts.join("@")
uname, = uname.split("+", 2)
condition == "#{uname}@#{domain}"
else
# Match as a domain
condition == address.split('@').last
condition == address.split("@").last
end
end

عرض الملف

@@ -23,41 +23,41 @@
class Organization < ApplicationRecord
RESERVED_PERMALINKS = ['new', 'edit', 'remove', 'delete', 'destroy', 'admin', 'mail', 'org', 'server']
RESERVED_PERMALINKS = ["new", "edit", "remove", "delete", "destroy", "admin", "mail", "org", "server"]
INITIAL_QUOTA = 10
INITIAL_SUPER_QUOTA = 10000
INITIAL_SUPER_QUOTA = 10_000
include HasUUID
include HasSoftDestroy
validates :name, :presence => true
validates :permalink, :presence => true, :format => {:with => /\A[a-z0-9\-]*\z/}, :uniqueness => true, :exclusion => {:in => RESERVED_PERMALINKS}
validates :time_zone, :presence => true
validates :name, presence: true
validates :permalink, presence: true, format: { with: /\A[a-z0-9-]*\z/ }, uniqueness: true, exclusion: { in: RESERVED_PERMALINKS }
validates :time_zone, presence: true
default_value :time_zone, -> { 'UTC' }
default_value :permalink, -> { Organization.find_unique_permalink(self.name) if self.name }
default_value :time_zone, -> { "UTC" }
default_value :permalink, -> { Organization.find_unique_permalink(name) if name }
belongs_to :owner, :class_name => 'User'
has_many :organization_users, :dependent => :destroy
has_many :users, :through => :organization_users, :source_type => 'User'
has_many :user_invites, :through => :organization_users, :source_type => 'UserInvite', :source => :user
has_many :servers, :dependent => :destroy
has_many :domains, :as => :owner, :dependent => :destroy
has_many :organization_ip_pools, :dependent => :destroy
has_many :ip_pools, :through => :organization_ip_pools
has_many :ip_pool_rules, :dependent => :destroy, :as => :owner
belongs_to :owner, class_name: "User"
has_many :organization_users, dependent: :destroy
has_many :users, through: :organization_users, source_type: "User"
has_many :user_invites, through: :organization_users, source_type: "UserInvite", source: :user
has_many :servers, dependent: :destroy
has_many :domains, as: :owner, dependent: :destroy
has_many :organization_ip_pools, dependent: :destroy
has_many :ip_pools, through: :organization_ip_pools
has_many :ip_pool_rules, dependent: :destroy, as: :owner
after_create do
if pool = IPPool.default
self.ip_pools << IPPool.default
ip_pools << IPPool.default
end
end
def status
if self.suspended?
'Suspended'
if suspended?
"Suspended"
else
'Active'
"Active"
end
end
@@ -71,25 +71,25 @@ class Organization < ApplicationRecord
def user_assignment(user)
@user_assignments ||= {}
@user_assignments[user.id] ||= organization_users.where(:user => user).first
@user_assignments[user.id] ||= organization_users.where(user: user).first
end
def make_owner(new_owner)
user_assignment(new_owner).update(:admin => true, :all_servers => true)
update(:owner => new_owner)
user_assignment(new_owner).update(admin: true, all_servers: true)
update(owner: new_owner)
end
# This is an array of addresses that should receive notifications for this organization
def notification_addresses
self.users.map(&:email_tag)
users.map(&:email_tag)
end
def self.find_unique_permalink(name)
loop.each_with_index do |_, i|
i = i + 1
i += 1
proposal = name.parameterize
proposal += "-#{i}" if i > 1
unless self.where(:permalink => proposal).exists?
unless where(permalink: proposal).exists?
return proposal
end
end
@@ -97,9 +97,9 @@ class Organization < ApplicationRecord
def self.[](id)
if id.is_a?(String)
where(:permalink => id).first
where(permalink: id).first
else
where(:id => id.to_i).first
where(id: id.to_i).first
end
end

عرض الملف

@@ -10,6 +10,8 @@
#
class OrganizationIPPool < ApplicationRecord
belongs_to :organization
belongs_to :ip_pool
end

عرض الملف

@@ -14,6 +14,6 @@
class OrganizationUser < ApplicationRecord
belongs_to :organization
belongs_to :user, :polymorphic => true, :optional => true
belongs_to :user, polymorphic: true, optional: true
end

عرض الملف

@@ -1,4 +1,4 @@
require 'resolv'
require "resolv"
class OutgoingMessagePrototype
@@ -29,9 +29,7 @@ class OutgoingMessagePrototype
end
end
def message_id
@message_id
end
attr_reader :message_id
def from_address
Postal::Helpers.strip_name_from_address(@from)
@@ -59,15 +57,15 @@ class OutgoingMessagePrototype
end
def to_addresses
@to.is_a?(String) ? @to.to_s.split(/\,\s*/) : @to.to_a
@to.is_a?(String) ? @to.to_s.split(/,\s*/) : @to.to_a
end
def cc_addresses
@cc.is_a?(String) ? @cc.to_s.split(/\,\s*/) : @cc.to_a
@cc.is_a?(String) ? @cc.to_s.split(/,\s*/) : @cc.to_a
end
def bcc_addresses
@bcc.is_a?(String) ? @bcc.to_s.split(/\,\s*/) : @bcc.to_a
@bcc.is_a?(String) ? @bcc.to_s.split(/,\s*/) : @bcc.to_a
end
def all_addresses
@@ -98,30 +96,30 @@ class OutgoingMessagePrototype
def attachments
(@attachments || []).map do |attachment|
{
:name => attachment[:name],
:content_type => attachment[:content_type] || 'application/octet-stream',
:data => attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data]
name: attachment[:name],
content_type: attachment[:content_type] || "application/octet-stream",
data: attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data]
}
end
end
def validate
@errors = Array.new
@errors = []
if to_addresses.empty? && cc_addresses.empty? && bcc_addresses.empty?
@errors << "NoRecipients"
end
if to_addresses.size > 50
@errors << 'TooManyToAddresses'
@errors << "TooManyToAddresses"
end
if cc_addresses.size > 50
@errors << 'TooManyCCAddresses'
@errors << "TooManyCCAddresses"
end
if bcc_addresses.size > 50
@errors << 'TooManyBCCAddresses'
@errors << "TooManyBCCAddresses"
end
if @plain_body.blank? && @html_body.blank?
@@ -136,7 +134,7 @@ class OutgoingMessagePrototype
@errors << "UnauthenticatedFromAddress"
end
if attachments && !attachments.empty?
if attachments.present?
attachments.each_with_index do |attachment, index|
if attachment[:name].blank?
@errors << "AttachmentMissingName" unless @errors.include?("AttachmentMissingName")
@@ -154,18 +152,18 @@ class OutgoingMessagePrototype
if @custom_headers.is_a?(Hash)
@custom_headers.each { |key, value| mail[key.to_s] = value.to_s }
end
mail.to = self.to_addresses.join(', ') if self.to_addresses.present?
mail.cc = self.cc_addresses.join(', ') if self.cc_addresses.present?
mail.to = to_addresses.join(", ") if to_addresses.present?
mail.cc = cc_addresses.join(", ") if cc_addresses.present?
mail.from = @from
mail.sender = @sender
mail.subject = @subject
mail.reply_to = @reply_to
mail.part :content_type => "multipart/alternative" do |p|
if !@plain_body.blank?
mail.part content_type: "multipart/alternative" do |p|
if @plain_body.present?
p.text_part = Mail::Part.new
p.text_part.body = @plain_body
end
if !@html_body.blank?
if @html_body.present?
p.html_part = Mail::Part.new
p.html_part.content_type = "text/html; charset=UTF-8"
p.html_part.body = @html_body
@@ -173,11 +171,11 @@ class OutgoingMessagePrototype
end
attachments.each do |attachment|
mail.attachments[attachment[:name]] = {
:mime_type => attachment[:content_type],
:content => attachment[:data]
mime_type: attachment[:content_type],
content: attachment[:data]
}
end
mail.header['Received'] = "from #{@source_type} (#{self.resolved_hostname} [#{@ip}]) by Postal with HTTP; #{Time.now.utc.rfc2822.to_s}"
mail.header["Received"] = "from #{@source_type} (#{resolved_hostname} [#{@ip}]) by Postal with HTTP; #{Time.now.utc.rfc2822}"
mail.message_id = "<#{@message_id}>"
mail.to_s
end
@@ -185,21 +183,25 @@ class OutgoingMessagePrototype
def create_message(address)
message = @server.message_db.new_message
message.scope = 'outgoing'
message.scope = "outgoing"
message.rcpt_to = address
message.mail_from = self.from_address
message.domain_id = self.domain.id
message.raw_message = self.raw_message
message.tag = self.tag
message.credential_id = self.credential&.id
message.mail_from = from_address
message.domain_id = domain.id
message.raw_message = raw_message
message.tag = tag
message.credential_id = credential&.id
message.received_with_ssl = true
message.bounce = @bounce ? 1 : 0
message.save
{:id => message.id, :token => message.token}
{ id: message.id, token: message.token }
end
def resolved_hostname
@resolved_hostname ||= Resolv.new.getname(@ip) rescue @ip
@resolved_hostname ||= begin
Resolv.new.getname(@ip)
rescue StandardError
@ip
end
end
end

عرض الملف

@@ -29,21 +29,21 @@ class QueuedMessage < ApplicationRecord
include HasMessage
belongs_to :server
belongs_to :ip_address, :optional => true
belongs_to :user, :optional => true
belongs_to :ip_address, optional: true
belongs_to :user, optional: true
before_create :allocate_ip_address
after_commit :queue, :on => :create
after_commit :queue, on: :create
scope :unlocked, -> { where(:locked_at => nil) }
scope :unlocked, -> { where(locked_at: nil) }
scope :retriable, -> { where("retry_after IS NULL OR retry_after <= ?", 30.seconds.from_now) }
def retriable?
self.retry_after.nil? || self.retry_after <= 30.seconds.from_now
retry_after.nil? || retry_after <= 30.seconds.from_now
end
def queue
UnqueueMessageJob.queue(queue_name, :id => self.id)
UnqueueMessageJob.queue(queue_name, id: id)
end
def queue!
@@ -56,21 +56,21 @@ class QueuedMessage < ApplicationRecord
end
def send_bounce
if self.message.send_bounces?
Postal::BounceMessage.new(self.server, self.message).queue
end
return unless message.send_bounces?
Postal::BounceMessage.new(server, message).queue
end
def allocate_ip_address
if Postal.ip_pools? && self.message && pool = self.server.ip_pool_for_message(self.message)
self.ip_address = pool.ip_addresses.select_by_priority
end
return unless Postal.ip_pools? && message && pool = server.ip_pool_for_message(message)
self.ip_address = pool.ip_addresses.select_by_priority
end
def acquire_lock
time = Time.now
locker = Postal.locker_name
rows = self.class.where(:id => self.id, :locked_by => nil, :locked_at => nil).update_all(:locked_by => locker, :locked_at => time)
rows = self.class.where(id: id, locked_by: nil, locked_at: nil).update_all(locked_by: locker, locked_at: time)
if rows == 1
self.locked_by = locker
self.locked_at = time
@@ -81,20 +81,20 @@ class QueuedMessage < ApplicationRecord
end
def retry_later(time = nil)
retry_time = time || self.class.calculate_retry_time(self.attempts, 5.minutes)
retry_time = time || self.class.calculate_retry_time(attempts, 5.minutes)
self.locked_by = nil
self.locked_at = nil
update_columns(:locked_by => nil, :locked_at => nil, :retry_after => Time.now + retry_time, :attempts => self.attempts + 1)
update_columns(locked_by: nil, locked_at: nil, retry_after: Time.now + retry_time, attempts: attempts + 1)
end
def unlock
self.locked_by = nil
self.locked_at = nil
update_columns(:locked_by => nil, :locked_at => nil)
update_columns(locked_by: nil, locked_at: nil)
end
def self.calculate_retry_time(attempts, initial_period)
(1.3 ** attempts) * initial_period
(1.3**attempts) * initial_period
end
def locked?
@@ -105,13 +105,14 @@ class QueuedMessage < ApplicationRecord
unless locked?
raise Postal::Error, "Must lock current message before locking any friends"
end
if self.batch_key.nil?
if batch_key.nil?
[]
else
time = Time.now
locker = Postal.locker_name
self.class.retriable.where(:batch_key => self.batch_key, :ip_address_id => self.ip_address_id, :locked_by => nil, :locked_at => nil).limit(limit).update_all(:locked_by => locker, :locked_at => time)
QueuedMessage.where(:batch_key => self.batch_key, :ip_address_id => self.ip_address_id, :locked_by => locker, :locked_at => time).where.not(id: self.id)
self.class.retriable.where(batch_key: batch_key, ip_address_id: ip_address_id, locked_by: nil, locked_at: nil).limit(limit).update_all(locked_by: locker, locked_at: time)
QueuedMessage.where(batch_key: batch_key, ip_address_id: ip_address_id, locked_by: locker, locked_at: time).where.not(id: id)
end
end

عرض الملف

@@ -22,22 +22,22 @@
class Route < ApplicationRecord
MODES = ['Endpoint', 'Accept', 'Hold', 'Bounce', 'Reject']
MODES = ["Endpoint", "Accept", "Hold", "Bounce", "Reject"]
include HasUUID
belongs_to :server
belongs_to :domain, :optional => true
belongs_to :endpoint, :polymorphic => true, :optional => true
has_many :additional_route_endpoints, :dependent => :destroy
belongs_to :domain, optional: true
belongs_to :endpoint, polymorphic: true, optional: true
has_many :additional_route_endpoints, dependent: :destroy
SPAM_MODES = ['Mark', 'Quarantine', 'Fail']
ENDPOINT_TYPES = ['SMTPEndpoint', 'HTTPEndpoint', 'AddressEndpoint']
SPAM_MODES = ["Mark", "Quarantine", "Fail"]
ENDPOINT_TYPES = ["SMTPEndpoint", "HTTPEndpoint", "AddressEndpoint"]
validates :name, :presence => true, :format => /\A(([a-z0-9\-\.]*)|(\*)|(__returnpath__))\z/
validates :spam_mode, :inclusion => {:in => SPAM_MODES}
validates :endpoint, :presence => {:if => proc { self.mode == 'Endpoint' }}
validates :domain_id, :presence => {:unless => :return_path?}
validates :name, presence: true, format: /\A(([a-z0-9\-.]*)|(\*)|(__returnpath__))\z/
validates :spam_mode, inclusion: { in: SPAM_MODES }
validates :endpoint, presence: { if: proc { mode == "Endpoint" } }
validates :domain_id, presence: { unless: :return_path? }
validate :validate_route_is_routed
validate :validate_domain_belongs_to_server
validate :validate_endpoint_belongs_to_server
@@ -47,7 +47,7 @@ class Route < ApplicationRecord
after_save :save_additional_route_endpoints
random_string :token, :type => :chars, :length => 8, :unique => true
random_string :token, type: :chars, length: 8, unique: true
def return_path?
name == "__returnpath__"
@@ -62,12 +62,10 @@ class Route < ApplicationRecord
end
def _endpoint
@endpoint ||= begin
if self.mode == 'Endpoint'
endpoint ? "#{endpoint.class}##{endpoint.uuid}" : nil
else
self.mode
end
if mode == "Endpoint"
@endpoint ||= endpoint ? "#{endpoint.class}##{endpoint.uuid}" : nil
else
@endpoint ||= mode
end
end
@@ -75,18 +73,17 @@ class Route < ApplicationRecord
if value.blank?
self.endpoint = nil
self.mode = nil
else
if value =~ /\#/
class_name, id = value.split('#', 2)
unless ENDPOINT_TYPES.include?(class_name)
raise Postal::Error, "Invalid endpoint class name '#{class_name}'"
end
self.endpoint = class_name.constantize.find_by_uuid(id)
self.mode = 'Endpoint'
else
self.endpoint = nil
self.mode = value
elsif value =~ /\#/
class_name, id = value.split("#", 2)
unless ENDPOINT_TYPES.include?(class_name)
raise Postal::Error, "Invalid endpoint class name '#{class_name}'"
end
self.endpoint = class_name.constantize.find_by_uuid(id)
self.mode = "Endpoint"
else
self.endpoint = nil
self.mode = value
end
end
@@ -95,7 +92,7 @@ class Route < ApplicationRecord
end
def wildcard?
self.name == '*'
name == "*"
end
def additional_route_endpoints_array
@@ -107,50 +104,51 @@ class Route < ApplicationRecord
end
def save_additional_route_endpoints
if @additional_route_endpoints_array
seen = []
@additional_route_endpoints_array.each do |item|
if existing = additional_route_endpoints.find_by_endpoint(item)
seen << existing.id
return unless @additional_route_endpoints_array
seen = []
@additional_route_endpoints_array.each do |item|
if existing = additional_route_endpoints.find_by_endpoint(item)
seen << existing.id
else
route = additional_route_endpoints.build(_endpoint: item)
if route.save
seen << route.id
else
route = additional_route_endpoints.build(:_endpoint => item)
if route.save
seen << route.id
else
route.errors.each do |field, message|
errors.add :base, message
end
raise ActiveRecord::RecordInvalid
route.errors.each do |field, message|
errors.add :base, message
end
raise ActiveRecord::RecordInvalid
end
end
additional_route_endpoints.where.not(:id => seen).destroy_all
end
additional_route_endpoints.where.not(id: seen).destroy_all
end
#
# This message will create a suitable number of message objects for messages that
# are destined for this route. It receives a block which can set the message content
#  are destined for this route. It receives a block which can set the message content
# but most information is specified already.
#
# Returns an array of created messages.
#
def create_messages(&block)
messages = []
message = self.build_message
if self.mode == 'Endpoint' && self.server.message_db.schema_version >= 18
message.endpoint_type = self.endpoint_type
message.endpoint_id = self.endpoint_id
message = build_message
if mode == "Endpoint" && server.message_db.schema_version >= 18
message.endpoint_type = endpoint_type
message.endpoint_id = endpoint_id
end
block.call(message)
message.save
messages << message
# Also create any messages for additional endpoints that might exist
if self.mode == 'Endpoint' && self.server.message_db.schema_version >= 18
self.additional_route_endpoints.each do |endpoint|
if mode == "Endpoint" && server.message_db.schema_version >= 18
additional_route_endpoints.each do |endpoint|
next unless endpoint.endpoint
message = self.build_message
message = build_message
message.endpoint_id = endpoint.endpoint_id
message.endpoint_type = endpoint.endpoint_type
block.call(message)
@@ -163,69 +161,67 @@ class Route < ApplicationRecord
end
def build_message
message = self.server.message_db.new_message
message.scope = 'incoming'
message.rcpt_to = self.description
message.domain_id = self.domain&.id
message.route_id = self.id
message = server.message_db.new_message
message.scope = "incoming"
message.rcpt_to = description
message.domain_id = domain&.id
message.route_id = id
message
end
private
def validate_route_is_routed
if self.mode.nil?
errors.add :endpoint, "must be chosen"
end
return unless mode.nil?
errors.add :endpoint, "must be chosen"
end
def validate_domain_belongs_to_server
if self.domain && ![self.server, self.server.organization].include?(self.domain.owner)
if domain && ![server, server.organization].include?(domain.owner)
errors.add :domain, :invalid
end
if self.domain && !self.domain.verified?
errors.add :domain, "has not been verified yet"
end
return unless domain && !domain.verified?
errors.add :domain, "has not been verified yet"
end
def validate_endpoint_belongs_to_server
if self.endpoint && self.endpoint&.server != self.server
errors.add :endpoint, :invalid
end
return unless endpoint && endpoint&.server != server
errors.add :endpoint, :invalid
end
def validate_name_uniqueness
return if self.server.nil?
if self.domain
if route = Route.includes(:domain).where(:domains => {:name => self.domain.name}, :name => self.name).where.not(:id => self.id).first
return if server.nil?
if domain
if route = Route.includes(:domain).where(domains: { name: domain.name }, name: name).where.not(id: id).first
errors.add :name, "is configured on the #{route.server.full_permalink} mail server"
end
else
if route = Route.where(:name => "__returnpath__").where.not(:id => self.id).exists?
errors.add :base, "A return path route already exists for this server"
end
elsif route = Route.where(name: "__returnpath__").where.not(id: id).exists?
errors.add :base, "A return path route already exists for this server"
end
end
def validate_return_path_route_endpoints
if return_path?
if self.mode != 'Endpoint' || self.endpoint_type != 'HTTPEndpoint'
errors.add :base, "Return path routes must point to an HTTP endpoint"
end
end
return unless return_path?
return unless mode != "Endpoint" || endpoint_type != "HTTPEndpoint"
errors.add :base, "Return path routes must point to an HTTP endpoint"
end
def validate_no_additional_routes_on_non_endpoint_route
if self.mode != 'Endpoint' && !self.additional_route_endpoints_array.empty?
errors.add :base, "Additional routes are not permitted unless the primary route is an actual endpoint"
end
return unless mode != "Endpoint" && !additional_route_endpoints_array.empty?
errors.add :base, "Additional routes are not permitted unless the primary route is an actual endpoint"
end
def self.find_by_name_and_domain(name, domain)
route = Route.includes(:domain).where(:name => name, :domains => {:name => domain}).first
route = Route.includes(:domain).where(name: name, domains: { name: domain }).first
if route.nil?
route = Route.includes(:domain).where(:name => '*', :domains => {:name => domain}).first
route = Route.includes(:domain).where(name: "*", domains: { name: domain }).first
end
route
end

عرض الملف

@@ -41,7 +41,7 @@
class Server < ApplicationRecord
RESERVED_PERMALINKS = ['new', 'all', 'search', 'stats', 'edit', 'manage', 'delete', 'destroy', 'remove']
RESERVED_PERMALINKS = ["new", "all", "search", "stats", "edit", "manage", "delete", "destroy", "remove"]
include HasUUID
include HasSoftDestroy
@@ -49,55 +49,55 @@ class Server < ApplicationRecord
attr_accessor :provision_database
belongs_to :organization
belongs_to :ip_pool, :optional => true
has_many :domains, :dependent => :destroy, :as => :owner
has_many :credentials, :dependent => :destroy
has_many :smtp_endpoints, :dependent => :destroy
has_many :http_endpoints, :dependent => :destroy
has_many :address_endpoints, :dependent => :destroy
has_many :routes, :dependent => :destroy
has_many :queued_messages, :dependent => :delete_all
has_many :webhooks, :dependent => :destroy
has_many :webhook_requests, :dependent => :destroy
has_many :track_domains, :dependent => :destroy
has_many :ip_pool_rules, :dependent => :destroy, :as => :owner
belongs_to :ip_pool, optional: true
has_many :domains, dependent: :destroy, as: :owner
has_many :credentials, dependent: :destroy
has_many :smtp_endpoints, dependent: :destroy
has_many :http_endpoints, dependent: :destroy
has_many :address_endpoints, dependent: :destroy
has_many :routes, dependent: :destroy
has_many :queued_messages, dependent: :delete_all
has_many :webhooks, dependent: :destroy
has_many :webhook_requests, dependent: :destroy
has_many :track_domains, dependent: :destroy
has_many :ip_pool_rules, dependent: :destroy, as: :owner
MODES = ['Live', 'Development']
MODES = ["Live", "Development"]
random_string :token, :type => :chars, :length => 6, :unique => true, :upper_letters_only => true
default_value :permalink, -> { name ? name.parameterize : nil}
random_string :token, type: :chars, length: 6, unique: true, upper_letters_only: true
default_value :permalink, -> { name ? name.parameterize : nil }
default_value :raw_message_retention_days, -> { 30 }
default_value :raw_message_retention_size, -> { 2048 }
default_value :message_retention_days, -> { 60 }
default_value :spam_threshold, -> { Postal.config.general.default_spam_threshold }
default_value :spam_failure_threshold, -> { Postal.config.general.default_spam_failure_threshold }
validates :name, :presence => true, :uniqueness => {:scope => :organization_id}
validates :mode, :inclusion => {:in => MODES}
validates :permalink, :presence => true, :uniqueness => {:scope => :organization_id}, :format => {:with => /\A[a-z0-9\-]*\z/}, :exclusion => {:in => RESERVED_PERMALINKS}
validates :name, presence: true, uniqueness: { scope: :organization_id }
validates :mode, inclusion: { in: MODES }
validates :permalink, presence: true, uniqueness: { scope: :organization_id }, format: { with: /\A[a-z0-9-]*\z/ }, exclusion: { in: RESERVED_PERMALINKS }
validate :validate_ip_pool_belongs_to_organization
before_validation(:on => :create) do
self.token = self.token.downcase if self.token
before_validation(on: :create) do
self.token = token.downcase if token
end
after_create do
unless self.provision_database == false
unless provision_database == false
message_db.provisioner.provision
end
end
after_commit(:on => :destroy) do
unless self.provision_database == false
after_commit(on: :destroy) do
unless provision_database == false
message_db.provisioner.drop
end
end
def status
if self.suspended?
'Suspended'
if suspended?
"Suspended"
else
self.mode
mode
end
end
@@ -110,12 +110,12 @@ class Server < ApplicationRecord
end
def actual_suspension_reason
if suspended?
if suspended_at.nil?
organization.suspension_reason
else
self.suspension_reason
end
return unless suspended?
if suspended_at.nil?
organization.suspension_reason
else
suspension_reason
end
end
@@ -124,30 +124,28 @@ class Server < ApplicationRecord
end
def message_db
@message_db ||= Postal::MessageDB::Database.new(self.organization_id, self.id)
@message_db ||= Postal::MessageDB::Database.new(organization_id, id)
end
def message(id)
message_db.message(id)
end
delegate :message, to: :message_db
def message_rate
@message_rate ||= message_db.live_stats.total(60, :types => [:incoming, :outgoing]) / 60.0
@message_rate ||= message_db.live_stats.total(60, types: [:incoming, :outgoing]) / 60.0
end
def held_messages
@held_messages ||= message_db.messages(:where => {:held => 1}, :count => true)
@held_messages ||= message_db.messages(where: { held: 1 }, count: true)
end
def throughput_stats
@throughput_stats ||= begin
incoming = message_db.live_stats.total(60, :types => [:incoming])
outgoing = message_db.live_stats.total(60, :types => [:outgoing])
incoming = message_db.live_stats.total(60, types: [:incoming])
outgoing = message_db.live_stats.total(60, types: [:outgoing])
outgoing_usage = send_limit ? (outgoing / send_limit.to_f) * 100 : 0
{
:incoming => incoming,
:outgoing => outgoing,
:outgoing_usage => outgoing_usage
incoming: incoming,
outgoing: outgoing,
outgoing_usage: outgoing_usage
}
end
end
@@ -166,8 +164,10 @@ class Server < ApplicationRecord
end
def domain_stats
domains = Domain.where(:owner_id => self.id, :owner_type => 'Server').to_a
total, unverified, bad_dns = 0, 0, 0
domains = Domain.where(owner_id: id, owner_type: "Server").to_a
total = 0
unverified = 0
bad_dns = 0
domains.each do |domain|
total += 1
unverified += 1 unless domain.verified?
@@ -178,29 +178,29 @@ class Server < ApplicationRecord
def webhook_hash
{
:uuid => self.uuid,
:name => self.name,
:permalink => self.permalink,
:organization => self.organization&.permalink
uuid: uuid,
name: name,
permalink: permalink,
organization: organization&.permalink
}
end
def send_volume
@send_volume ||= message_db.live_stats.total(60, :types => [:outgoing])
@send_volume ||= message_db.live_stats.total(60, types: [:outgoing])
end
def send_limit_approaching?
self.send_limit && (send_volume >= self.send_limit * 0.90)
send_limit && (send_volume >= send_limit * 0.90)
end
def send_limit_exceeded?
self.send_limit && send_volume >= self.send_limit
send_limit && send_volume >= send_limit
end
def send_limit_warning(type)
AppMailer.send("server_send_limit_#{type}", self).deliver
self.update_column("send_limit_#{type}_notified_at", Time.now)
WebhookRequest.trigger(self, "SendLimit#{type.to_s.capitalize}", :server => webhook_hash, :volume => self.send_volume, :limit => self.send_limit)
update_column("send_limit_#{type}_notified_at", Time.now)
WebhookRequest.trigger(self, "SendLimit#{type.to_s.capitalize}", server: webhook_hash, volume: send_volume, limit: send_limit)
end
def queue_size
@@ -209,36 +209,38 @@ class Server < ApplicationRecord
def stats
{
:queue => queue_size,
:held => self.held_messages,
:bounce_rate => self.bounce_rate,
:message_rate => self.message_rate,
:throughput => self.throughput_stats,
:size => self.message_db.total_size
queue: queue_size,
held: held_messages,
bounce_rate: bounce_rate,
message_rate: message_rate,
throughput: throughput_stats,
size: message_db.total_size
}
end
def authenticated_domain_for_address(address)
return nil if address.blank?
address = Postal::Helpers.strip_name_from_address(address)
uname, domain_name = address.split('@', 2)
uname, domain_name = address.split("@", 2)
return nil unless uname
return nil unless domain_name
uname, _ = uname.split('+', 2)
uname, = uname.split("+", 2)
# Check the server's domain
if domain = Domain.verified.order(:owner_type => :desc).where("(owner_type = 'Organization' AND owner_id = ?) OR (owner_type = 'Server' AND owner_id = ?)", self.organization_id, self.id).where(:name => domain_name).first
if domain = Domain.verified.order(owner_type: :desc).where("(owner_type = 'Organization' AND owner_id = ?) OR (owner_type = 'Server' AND owner_id = ?)", organization_id, id).where(name: domain_name).first
return domain
end
if any_domain = self.domains.verified.where(:use_for_any => true).order(:name).first
return any_domain
end
return unless any_domain = domains.verified.where(use_for_any: true).order(:name).first
any_domain
end
def find_authenticated_domain_from_headers(headers)
header_to_check = ['from']
header_to_check << 'sender' if self.allow_sender?
header_to_check = ["from"]
header_to_check << "sender" if allow_sender?
header_to_check.each do |header_name|
if headers[header_name].is_a?(Array)
values = headers[header_name]
@@ -257,36 +259,34 @@ class Server < ApplicationRecord
def suspend(reason)
self.suspended_at = Time.now
self.suspension_reason = reason
self.save!
save!
AppMailer.server_suspended(self).deliver
end
def unsuspend
self.suspended_at = nil
self.suspension_reason = nil
self.save!
save!
end
def validate_ip_pool_belongs_to_organization
if self.ip_pool && self.ip_pool_id_changed? && !self.organization.ip_pools.include?(self.ip_pool)
errors.add :ip_pool_id, "must belong to the organization"
end
return unless ip_pool && ip_pool_id_changed? && !organization.ip_pools.include?(ip_pool)
errors.add :ip_pool_id, "must belong to the organization"
end
def ip_pool_for_message(message)
if message.scope == 'outgoing'
[self, self.organization].each do |scope|
rules = scope.ip_pool_rules.order(:created_at => :desc)
rules.each do |rule|
if rule.apply_to_message?(message)
return rule.ip_pool
end
return unless message.scope == "outgoing"
[self, organization].each do |scope|
rules = scope.ip_pool_rules.order(created_at: :desc)
rules.each do |rule|
if rule.apply_to_message?(message)
return rule.ip_pool
end
end
self.ip_pool
else
nil
end
ip_pool
end
def self.triggered_send_limit(type)
@@ -297,12 +297,12 @@ class Server < ApplicationRecord
def self.send_send_limit_notifications
[:approaching, :exceeded].each_with_object({}) do |type, hash|
hash[type] = 0
servers = self.triggered_send_limit(type)
unless servers.empty?
servers.each do |server|
hash[type] += 1
server.send_limit_warning(type)
end
servers = triggered_send_limit(type)
next if servers.empty?
servers.each do |server|
hash[type] += 1
server.send_limit_warning(type)
end
end
end
@@ -311,15 +311,15 @@ class Server < ApplicationRecord
server = nil
if id.is_a?(String)
if id =~ /\A(\w+)\/(\w+)\z/
server = includes(:organization).where(:organizations => {:permalink => $1}, :permalink => $2).first
server = includes(:organization).where(organizations: { permalink: ::Regexp.last_match(1) }, permalink: ::Regexp.last_match(2)).first
end
else
server = where(:id => id).first
server = where(id: id).first
end
if extra
if extra.is_a?(String)
server.domains.where(:name => extra.to_s).first
server.domains.where(name: extra.to_s).first
else
server.message(extra.to_i)
end

عرض الملف

@@ -21,17 +21,17 @@ class SMTPEndpoint < ApplicationRecord
include HasUUID
belongs_to :server
has_many :routes, :as => :endpoint
has_many :additional_route_endpoints, :dependent => :destroy, :as => :endpoint
has_many :routes, as: :endpoint
has_many :additional_route_endpoints, dependent: :destroy, as: :endpoint
SSL_MODES = ['None', 'Auto', 'STARTTLS', 'TLS']
SSL_MODES = ["None", "Auto", "STARTTLS", "TLS"]
before_destroy :update_routes
validates :name, :presence => true
validates :hostname, :presence => true, :format => /\A[a-z0-9\.\-]*\z/
validates :ssl_mode, :inclusion => {:in => SSL_MODES}
validates :port, :numericality => {:only_integer => true, :allow_blank => true}
validates :name, presence: true
validates :hostname, presence: true, format: /\A[a-z0-9.-]*\z/
validates :ssl_mode, inclusion: { in: SSL_MODES }
validates :port, numericality: { only_integer: true, allow_blank: true }
def description
"#{name} (#{hostname})"
@@ -42,7 +42,7 @@ class SMTPEndpoint < ApplicationRecord
end
def update_routes
self.routes.each { |r| r.update(:endpoint => nil, :mode => 'Reject') }
routes.each { |r| r.update(endpoint: nil, mode: "Reject") }
end
end

عرض الملف

@@ -18,7 +18,7 @@
# excluded_click_domains :text(65535)
#
require 'resolv'
require "resolv"
class TrackDomain < ApplicationRecord
@@ -27,16 +27,16 @@ class TrackDomain < ApplicationRecord
belongs_to :server
belongs_to :domain
validates :name, :presence => true, :format => {:with => /\A[a-z0-9\-]+\z/}, :uniqueness => {:scope => :domain_id, :message => "is already added"}
validates :domain_id, :uniqueness => {:scope => :server_id, :message => "already has a track domain for this server"}
validates :name, presence: true, format: { with: /\A[a-z0-9-]+\z/ }, uniqueness: { scope: :domain_id, message: "is already added" }
validates :domain_id, uniqueness: { scope: :server_id, message: "already has a track domain for this server" }
validate :validate_domain_belongs_to_server
scope :ok, -> { where(:dns_status => 'OK')}
scope :ok, -> { where(dns_status: "OK") }
after_create :check_dns, :unless => :dns_status
after_create :check_dns, unless: :dns_status
before_validation do
self.server = self.domain.server if self.domain && self.server.nil?
self.server = domain.server if domain && server.nil?
end
def full_name
@@ -48,26 +48,24 @@ class TrackDomain < ApplicationRecord
end
def dns_ok?
self.dns_status == 'OK'
dns_status == "OK"
end
def check_dns
result = self.domain.resolver.getresources(self.full_name, Resolv::DNS::Resource::IN::CNAME)
result = domain.resolver.getresources(full_name, Resolv::DNS::Resource::IN::CNAME)
records = result.map { |r| r.name.to_s.downcase }
if records.empty?
self.dns_status = 'Missing'
self.dns_error = "There is no record at #{self.full_name}"
self.dns_status = "Missing"
self.dns_error = "There is no record at #{full_name}"
elsif records.size == 1 && records.first == Postal.config.dns.track_domain
self.dns_status = "OK"
self.dns_error = nil
else
if records.size == 1 && records.first == Postal.config.dns.track_domain
self.dns_status = 'OK'
self.dns_error = nil
else
self.dns_status = 'Invalid'
self.dns_error = "There is a CNAME record at #{self.full_name} but it points to #{records.first} which is incorrect. It should point to #{Postal.config.dns.track_domain}."
end
self.dns_status = "Invalid"
self.dns_error = "There is a CNAME record at #{full_name} but it points to #{records.first} which is incorrect. It should point to #{Postal.config.dns.track_domain}."
end
self.dns_checked_at = Time.now
self.save!
save!
dns_ok?
end
@@ -76,9 +74,9 @@ class TrackDomain < ApplicationRecord
end
def validate_domain_belongs_to_server
if self.domain && ![self.server, self.server.organization].include?(self.domain.owner)
errors.add :domain, "does not belong to the server or the server's organization"
end
return unless domain && ![server, server.organization].include?(domain.owner)
errors.add :domain, "does not belong to the server or the server's organization"
end
end

عرض الملف

@@ -27,25 +27,23 @@ class User < ApplicationRecord
include HasUUID
require_dependency 'user/authentication'
require_dependency "user/authentication"
validates :first_name, :presence => true
validates :last_name, :presence => true
validates :email_address, :presence => true, :uniqueness => true, :format => {:with => /@/, allow_blank: true}
validates :time_zone, :presence => true
validates :first_name, presence: true
validates :last_name, presence: true
validates :email_address, presence: true, uniqueness: true, format: { with: /@/, allow_blank: true }
validates :time_zone, presence: true
default_value :time_zone, -> { 'UTC' }
default_value :time_zone, -> { "UTC" }
has_many :organization_users, :dependent => :destroy, :as => :user
has_many :organizations, :through => :organization_users
has_many :organization_users, dependent: :destroy, as: :user
has_many :organizations, through: :organization_users
def organizations_scope
@organizations_scope ||= begin
if self.admin?
Organization.present
else
self.organizations.present
end
if admin?
@organizations_scope ||= Organization.present
else
@organizations_scope ||= organizations.present
end
end
@@ -70,7 +68,7 @@ class User < ApplicationRecord
end
def self.[](email)
where(:email_address => email).first
where(email_address: email).first
end
end

عرض الملف

@@ -2,9 +2,9 @@ class User
has_secure_password
validates :password, :length => {:minimum => 8, :allow_blank => true}
validates :password, length: { minimum: 8, allow_blank: true }
when_attribute :password_digest, :changes_to => :anything do
when_attribute :password_digest, changes_to: :anything do
before_save do
self.password_reset_token = nil
self.password_reset_token_valid_until = nil
@@ -12,9 +12,10 @@ class User
end
def self.authenticate(email_address, password)
user = where(:email_address => email_address).first
raise Postal::Errors::AuthenticationError.new('InvalidEmailAddress') if user.nil?
raise Postal::Errors::AuthenticationError.new('InvalidPassword') unless user.authenticate(password)
user = where(email_address: email_address).first
raise Postal::Errors::AuthenticationError, "InvalidEmailAddress" if user.nil?
raise Postal::Errors::AuthenticationError, "InvalidPassword" unless user.authenticate(password)
user
end
@@ -27,9 +28,9 @@ class User
end
def begin_password_reset(return_to = nil)
self.password_reset_token = Nifty::Utils::RandomString.generate(:length => 24)
self.password_reset_token = Nifty::Utils::RandomString.generate(length: 24)
self.password_reset_token_valid_until = 1.day.from_now
self.save!
save!
AppMailer.password_reset(self, return_to).deliver
end

عرض الملف

@@ -18,10 +18,10 @@ class UserInvite < ApplicationRecord
include HasUUID
validates :email_address, :presence => true, :uniqueness => true, :format => {:with => /@/, :allow_blank => true}
validates :email_address, presence: true, uniqueness: true, format: { with: /@/, allow_blank: true }
has_many :organization_users, :dependent => :destroy, :as => :user
has_many :organizations, :through => :organization_users
has_many :organization_users, dependent: :destroy, as: :user
has_many :organizations, through: :organization_users
default_value :expires_at, -> { 7.days.from_now }
@@ -41,16 +41,16 @@ class UserInvite < ApplicationRecord
def accept(user)
transaction do
self.organization_users.each do |ou|
ou.update(:user => user) || ou.destroy
organization_users.each do |ou|
ou.update(user: user) || ou.destroy
end
self.organization_users.reload
self.destroy
organization_users.reload
destroy
end
end
def reject
self.destroy
destroy
end
end

عرض الملف

@@ -24,19 +24,19 @@ class Webhook < ApplicationRecord
include HasUUID
belongs_to :server
has_many :webhook_events, :dependent => :destroy
has_many :webhook_events, dependent: :destroy
has_many :webhook_requests
validates :name, :presence => true
validates :url, :presence => true, :format => {:with => /\Ahttps?\:\/\/[a-z0-9\-\.\_\?\=\&\/\+:%@]+\z/i, :allow_blank => true}
validates :name, presence: true
validates :url, presence: true, format: { with: /\Ahttps?:\/\/[a-z0-9\-._?=&\/+:%@]+\z/i, allow_blank: true }
scope :enabled, -> { where(:enabled => true) }
scope :enabled, -> { where(enabled: true) }
after_save :save_events
when_attribute :all_events, :changes_to => true do
when_attribute :all_events, changes_to: true do
after_save do
self.webhook_events.destroy_all
webhook_events.destroy_all
end
end
@@ -49,12 +49,12 @@ class Webhook < ApplicationRecord
end
def save_events
if @events
@events.each do |event|
webhook_events.where(:event => event).first_or_create!
end
webhook_events.where.not(:event => @events).destroy_all
return unless @events
@events.each do |event|
webhook_events.where(event: event).first_or_create!
end
webhook_events.where.not(event: @events).destroy_all
end
end

عرض الملف

@@ -15,18 +15,18 @@
class WebhookEvent < ApplicationRecord
EVENTS = [
'MessageSent',
'MessageDelayed',
'MessageDeliveryFailed',
'MessageHeld',
'MessageBounced',
'MessageLinkClicked',
'MessageLoaded',
'DomainDNSError'
"MessageSent",
"MessageDelayed",
"MessageDeliveryFailed",
"MessageHeld",
"MessageBounced",
"MessageLinkClicked",
"MessageLoaded",
"DomainDNSError"
]
belongs_to :webhook
validates :event, :presence => true
validates :event, presence: true
end

عرض الملف

@@ -19,17 +19,17 @@ class WebhookRequest < ApplicationRecord
include HasUUID
RETRIES = {1 => 2.minutes, 2 => 3.minutes, 3 => 6.minutes, 4 => 10.minutes, 5 => 15.minutes}
RETRIES = { 1 => 2.minutes, 2 => 3.minutes, 3 => 6.minutes, 4 => 10.minutes, 5 => 15.minutes }
belongs_to :server
belongs_to :webhook, :optional => true
belongs_to :webhook, optional: true
validates :url, :presence => true
validates :event, :presence => true
validates :url, presence: true
validates :event, presence: true
serialize :payload, Hash
after_commit :queue, :on => :create
after_commit :queue, on: :create
def self.trigger(server, event, payload = {})
unless server.is_a?(Server)
@@ -38,52 +38,52 @@ class WebhookRequest < ApplicationRecord
webhooks = server.webhooks.enabled.includes(:webhook_events).references(:webhook_events).where("webhooks.all_events = ? OR webhook_events.event = ?", true, event)
webhooks.each do |webhook|
server.webhook_requests.create!(:event => event, :payload => payload, :webhook => webhook, :url => webhook.url)
server.webhook_requests.create!(event: event, payload: payload, webhook: webhook, url: webhook.url)
end
end
def self.requeue_all
where("retry_after < ?", Time.now).each(&:queue)
where("retry_after < ?", Time.now).find_each(&:queue)
end
def queue
WebhookDeliveryJob.queue(:main, :id => self.id)
WebhookDeliveryJob.queue(:main, id: id)
end
def deliver
logger = Postal.logger_for(:webhooks)
payload = {:event => self.event, :timestamp => self.created_at.to_f, :payload => self.payload, :uuid => self.uuid}.to_json
logger.info "[#{id}] Sending webhook request to `#{self.url}`"
result = Postal::HTTP.post(self.url, :sign => true, :json => payload, :timeout => 5)
payload = { event: event, timestamp: created_at.to_f, payload: self.payload, uuid: uuid }.to_json
logger.info "[#{id}] Sending webhook request to `#{url}`"
result = Postal::HTTP.post(url, sign: true, json: payload, timeout: 5)
self.attempts += 1
self.retry_after = RETRIES[self.attempts]&.from_now
self.server.message_db.webhooks.record(
:event => self.event,
:url => self.url,
:webhook_id => self.webhook_id,
:attempt => self.attempts,
:timestamp => Time.now.to_f,
:payload => self.payload.to_json,
:uuid => self.uuid,
:status_code => result[:code],
:body => result[:body],
:will_retry => (self.retry_after ? 0 : 1)
server.message_db.webhooks.record(
event: event,
url: url,
webhook_id: webhook_id,
attempt: self.attempts,
timestamp: Time.now.to_f,
payload: self.payload.to_json,
uuid: uuid,
status_code: result[:code],
body: result[:body],
will_retry: (retry_after ? 0 : 1)
)
if result[:code] >= 200 && result[:code] < 300
logger.info "[#{id}] -> Received #{result[:code]} status code. That's OK."
self.destroy
self.webhook&.update_column(:last_used_at, Time.now)
destroy
webhook&.update_column(:last_used_at, Time.now)
true
else
logger.error "[#{id}] -> Received #{result[:code]} status code. That's not OK."
self.error = "Couldn't send to URL. Code received was #{result[:code]}"
if self.retry_after
logger.info "[#{id}] -> Will retry #{self.retry_after} (this was attempt #{self.attempts})"
self.save
if retry_after
logger.info "[#{id}] -> Will retry #{retry_after} (this was attempt #{self.attempts})"
save
else
logger.info "[#{id}] -> Have tried #{self.attempts} times. Giving up."
self.destroy
destroy
end
false
end