style(rubocop): fix all safe auto correctable offenses

This commit is contained in:
Charlie Smurthwaite
2023-03-16 15:50:53 +00:00
parent 02c93a4850
commit fd289c46fd
204 changed files with 2611 additions and 2486 deletions
+40 -40
View File
@@ -1,52 +1,52 @@
source 'https://rubygems.org' source "https://rubygems.org"
gem 'rails', '= 5.2.8.1' gem "authie", "~> 3.0"
gem 'mysql2' gem "autoprefixer-rails"
gem 'puma' gem "basic_ssl"
gem 'turbolinks', '~> 5' gem "bcrypt"
gem 'haml' gem "bunny"
gem 'nifty-utils' gem "changey"
gem 'nilify_blanks' gem "chronic"
gem 'kaminari' gem "clockwork"
gem 'bcrypt' gem "dotenv-rails"
gem 'foreman' gem "dynamic_form"
gem 'hashie' gem "encrypto_signo"
gem 'authie', '~> 3.0' gem "foreman"
gem 'dynamic_form' gem "gelf"
gem 'changey' gem "haml"
gem 'mail', :git => 'https://github.com/mikel/mail.git', :branch => '2-7-stable' gem "hashie"
gem 'autoprefixer-rails' gem "highline", require: false
gem 'bunny' gem "jwt"
gem 'secure_headers' gem "kaminari"
gem 'chronic' gem "mail", git: "https://github.com/mikel/mail.git", branch: "2-7-stable"
gem 'basic_ssl' gem "moonrope"
gem 'clockwork' gem "mysql2"
gem 'encrypto_signo' gem "nifty-utils"
gem 'nio4r' gem "nilify_blanks"
gem 'sentry-raven' gem "nio4r"
gem 'gelf' gem "puma"
gem 'moonrope' gem "rails", "= 5.2.8.1"
gem 'jwt' gem "resolv", "~> 0.2.1"
gem 'highline', :require => false gem "secure_headers"
gem 'resolv', '~> 0.2.1' gem "sentry-raven"
gem 'dotenv-rails' gem "turbolinks", "~> 5"
group :development, :assets do group :development, :assets do
gem 'sass-rails', '~> 5.0' gem "coffee-rails", "~> 4.2"
gem 'uglifier', '>= 1.3.0' gem "jquery-rails"
gem 'coffee-rails', '~> 4.2' gem "sass-rails", "~> 5.0"
gem 'jquery-rails' gem "uglifier", ">= 1.3.0"
end end
group :development, :test do group :development, :test do
gem 'byebug' gem "byebug"
end end
group :development do group :development do
gem 'annotate' gem "annotate"
gem 'rspec', require: false
gem 'rspec-rails', require: false
gem "factory_bot_rails", "~> 4.0", require: false
gem "database_cleaner", require: false gem "database_cleaner", require: false
gem "factory_bot_rails", "~> 4.0", require: false
gem "rspec", require: false
gem "rspec-rails", require: false
gem "rubocop" gem "rubocop"
gem "rubocop-rails" gem "rubocop-rails"
end end
+1 -1
View File
@@ -1,6 +1,6 @@
# Add your own tasks in files placed in lib/tasks ending in .rake, # Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative 'config/application' require_relative "config/application"
Rails.application.load_tasks Rails.application.load_tasks
+7 -7
View File
@@ -1,19 +1,19 @@
authenticator :server do authenticator :server do
friendly_name "Server Authenticator" friendly_name "Server Authenticator"
header "X-Server-API-Key", "The API token for a server that you wish to authenticate with.", :example => 'f29a45f0d4e1744ebaee' header "X-Server-API-Key", "The API token for a server that you wish to authenticate with.", example: "f29a45f0d4e1744ebaee"
error 'InvalidServerAPIKey', "The API token provided in X-Server-API-Key was not valid.", :attributes => {:token => "The token that was looked up"} error "InvalidServerAPIKey", "The API token provided in X-Server-API-Key was not valid.", attributes: { token: "The token that was looked up" }
error 'ServerSuspended', "The mail server has been suspended" error "ServerSuspended", "The mail server has been suspended"
lookup do lookup do
if key = request.headers['X-Server-API-Key'] if key = request.headers["X-Server-API-Key"]
if credential = Credential.where(:type => 'API', :key => key).first if credential = Credential.where(type: "API", key: key).first
if credential.server.suspended? if credential.server.suspended?
error 'ServerSuspended' error "ServerSuspended"
else else
credential.use credential.use
credential credential
end end
else else
error 'InvalidServerAPIKey', :token => key error "InvalidServerAPIKey", token: key
end end
end end
end end
+9 -10
View File
@@ -6,35 +6,34 @@ controller :messages do
action :message do action :message do
title "Return message details" title "Return message details"
description "Returns all details about a message" description "Returns all details about a message"
param :id, "The ID of the message", :type => Integer, :required => true param :id, "The ID of the message", type: Integer, required: true
returns Hash, :structure => :message, :structure_opts => {:paramable => {:expansions => false}} returns Hash, structure: :message, structure_opts: { paramable: { expansions: false } }
error 'MessageNotFound', "No message found matching provided ID", :attributes => {:id => "The ID of the message"} error "MessageNotFound", "No message found matching provided ID", attributes: { id: "The ID of the message" }
action do action do
begin begin
message = identity.server.message(params.id) message = identity.server.message(params.id)
rescue Postal::MessageDB::Message::NotFound => e rescue Postal::MessageDB::Message::NotFound => e
error 'MessageNotFound', :id => params.id error "MessageNotFound", id: params.id
end end
structure :message, message, :return => true structure :message, message, return: true
end end
end end
action :deliveries do action :deliveries do
title "Return deliveries for a message" title "Return deliveries for a message"
description "Returns an array of deliveries which have been attempted for this message" description "Returns an array of deliveries which have been attempted for this message"
param :id, "The ID of the message", :type => Integer, :required => true param :id, "The ID of the message", type: Integer, required: true
returns Array, :structure => :delivery, :structure_opts => {:full => true} returns Array, structure: :delivery, structure_opts: { full: true }
error 'MessageNotFound', "No message found matching provided ID", :attributes => {:id => "The ID of the message"} error "MessageNotFound", "No message found matching provided ID", attributes: { id: "The ID of the message" }
action do action do
begin begin
message = identity.server.message(params.id) message = identity.server.message(params.id)
rescue Postal::MessageDB::Message::NotFound => e rescue Postal::MessageDB::Message::NotFound => e
error 'MessageNotFound', :id => params.id error "MessageNotFound", id: params.id
end end
message.deliveries.map do |d| message.deliveries.map do |d|
structure :delivery, d structure :delivery, d
end end
end end
end end
end end
+37 -37
View File
@@ -7,30 +7,30 @@ controller :send do
title "Send a message" title "Send a message"
description "This action allows you to send a message by providing the appropriate options" description "This action allows you to send a message by providing the appropriate options"
# Acceptable Parameters # Acceptable Parameters
param :to, "The e-mail addresses of the recipients (max 50)", :type => Array param :to, "The e-mail addresses of the recipients (max 50)", type: Array
param :cc, "The e-mail addresses of any CC contacts (max 50)", :type => Array param :cc, "The e-mail addresses of any CC contacts (max 50)", type: Array
param :bcc, "The e-mail addresses of any BCC contacts (max 50)", :type => Array param :bcc, "The e-mail addresses of any BCC contacts (max 50)", type: Array
param :from, "The e-mail address for the From header", :type => String param :from, "The e-mail address for the From header", type: String
param :sender, "The e-mail address for the Sender header", :type => String param :sender, "The e-mail address for the Sender header", type: String
param :subject, "The subject of the e-mail", :type => String param :subject, "The subject of the e-mail", type: String
param :tag, "The tag of the e-mail", :type => String param :tag, "The tag of the e-mail", type: String
param :reply_to, "Set the reply-to address for the mail", :type => String param :reply_to, "Set the reply-to address for the mail", type: String
param :plain_body, "The plain text body of the e-mail", :type => String param :plain_body, "The plain text body of the e-mail", type: String
param :html_body, "The HTML body of the e-mail", :type => String param :html_body, "The HTML body of the e-mail", type: String
param :attachments, "An array of attachments for this e-mail", :type => Array param :attachments, "An array of attachments for this e-mail", type: Array
param :headers, "A hash of additional headers", :type => Hash param :headers, "A hash of additional headers", type: Hash
param :bounce, "Is this message a bounce?", :type => :boolean param :bounce, "Is this message a bounce?", type: :boolean
# Errors # Errors
error 'ValidationError', "The provided data was not sufficient to send an email", :attributes => {:errors => "A hash of error details"} error "ValidationError", "The provided data was not sufficient to send an email", attributes: { errors: "A hash of error details" }
error 'NoRecipients', "There are no recipients defined to receive this message" error "NoRecipients", "There are no recipients defined to receive this message"
error 'NoContent', "There is no content defined for this e-mail" error "NoContent", "There is no content defined for this e-mail"
error 'TooManyToAddresses', "The maximum number of To addresses has been reached (maximum 50)" error "TooManyToAddresses", "The maximum number of To addresses has been reached (maximum 50)"
error 'TooManyCCAddresses', "The maximum number of CC addresses has been reached (maximum 50)" error "TooManyCCAddresses", "The maximum number of CC addresses has been reached (maximum 50)"
error 'TooManyBCCAddresses', "The maximum number of BCC addresses has been reached (maximum 50)" error "TooManyBCCAddresses", "The maximum number of BCC addresses has been reached (maximum 50)"
error 'FromAddressMissing', "The From address is missing and is required" error "FromAddressMissing", "The From address is missing and is required"
error 'UnauthenticatedFromAddress', "The From address is not authorised to send mail from this server" error "UnauthenticatedFromAddress", "The From address is not authorised to send mail from this server"
error 'AttachmentMissingName', "An attachment is missing a name" error "AttachmentMissingName", "An attachment is missing a name"
error 'AttachmentMissingData', "An attachment is missing data" error "AttachmentMissingData", "An attachment is missing data"
# Return # Return
returns Hash returns Hash
# Action # Action
@@ -51,13 +51,14 @@ controller :send do
attributes[:attachments] = [] attributes[:attachments] = []
(params.attachments || []).each do |attachment| (params.attachments || []).each do |attachment|
next unless attachment.is_a?(Hash) next unless attachment.is_a?(Hash)
attributes[:attachments] << {:name => attachment['name'], :content_type => attachment['content_type'], :data => attachment['data'], :base64 => true}
attributes[:attachments] << { name: attachment["name"], content_type: attachment["content_type"], data: attachment["data"], base64: true }
end end
message = OutgoingMessagePrototype.new(identity.server, request.ip, 'api', attributes) message = OutgoingMessagePrototype.new(identity.server, request.ip, "api", attributes)
message.credential = identity message.credential = identity
if message.valid? if message.valid?
result = message.create_messages result = message.create_messages
{:message_id => message.message_id, :messages => result} { message_id: message.message_id, messages: result }
else else
error message.errors.first error message.errors.first
end end
@@ -67,44 +68,43 @@ controller :send do
action :raw do action :raw do
title "Send a raw RFC2882 message" title "Send a raw RFC2882 message"
description "This action allows you to send us a raw RFC2822 formatted message along with the recipients that it should be sent to. This is similar to sending a message through our SMTP service." description "This action allows you to send us a raw RFC2822 formatted message along with the recipients that it should be sent to. This is similar to sending a message through our SMTP service."
param :mail_from, "The address that should be logged as sending the message", :type => String, :required => true param :mail_from, "The address that should be logged as sending the message", type: String, required: true
param :rcpt_to, "The addresses this message should be sent to", :type => Array, :required => true param :rcpt_to, "The addresses this message should be sent to", type: Array, required: true
param :data, "A base64 encoded RFC2822 message to send", :type => String, :required => true param :data, "A base64 encoded RFC2822 message to send", type: String, required: true
param :bounce, "Is this message a bounce?", :type => :boolean param :bounce, "Is this message a bounce?", type: :boolean
returns Hash returns Hash
error 'UnauthenticatedFromAddress', "The From address is not authorised to send mail from this server" error "UnauthenticatedFromAddress", "The From address is not authorised to send mail from this server"
action do action do
# Decode the raw message # Decode the raw message
raw_message = Base64.decode64(params.data) raw_message = Base64.decode64(params.data)
# Parse through mail to get the from/sender headers # Parse through mail to get the from/sender headers
mail = Mail.new(raw_message.split("\r\n\r\n", 2).first) mail = Mail.new(raw_message.split("\r\n\r\n", 2).first)
from_headers = {'from' => mail.from, 'sender' => mail.sender} from_headers = { "from" => mail.from, "sender" => mail.sender }
authenticated_domain = identity.server.find_authenticated_domain_from_headers(from_headers) authenticated_domain = identity.server.find_authenticated_domain_from_headers(from_headers)
# If we're not authenticated, don't continue # If we're not authenticated, don't continue
if authenticated_domain.nil? if authenticated_domain.nil?
error 'UnauthenticatedFromAddress' error "UnauthenticatedFromAddress"
end end
# Store the result ready to return # Store the result ready to return
result = {:message_id => nil, :messages => {}} result = { message_id: nil, messages: {} }
params.rcpt_to.uniq.each do |rcpt_to| params.rcpt_to.uniq.each do |rcpt_to|
message = identity.server.message_db.new_message message = identity.server.message_db.new_message
message.rcpt_to = rcpt_to message.rcpt_to = rcpt_to
message.mail_from = params.mail_from message.mail_from = params.mail_from
message.raw_message = raw_message message.raw_message = raw_message
message.received_with_ssl = true message.received_with_ssl = true
message.scope = 'outgoing' message.scope = "outgoing"
message.domain_id = authenticated_domain.id message.domain_id = authenticated_domain.id
message.credential_id = identity.id message.credential_id = identity.id
message.bounce = params.bounce ? 1 : 0 message.bounce = params.bounce ? 1 : 0
message.save message.save
result[:message_id] = message.message_id if result[:message_id].nil? result[:message_id] = message.message_id if result[:message_id].nil?
result[:messages][rcpt_to] = {:id => message.id, :token => message.token} result[:messages][rcpt_to] = { id: message.id, token: message.token }
end end
result result
end end
end end
end end
+4 -4
View File
@@ -2,9 +2,9 @@ structure :delivery do
basic :id basic :id
basic :status basic :status
basic :details basic :details
basic :output, :value => proc { o.output&.strip } basic :output, value: proc { o.output&.strip }
basic :sent_with_ssl, :value => proc { o.sent_with_ssl == 1 } basic :sent_with_ssl, value: proc { o.sent_with_ssl == 1 }
basic :log_id basic :log_id
basic :time, :value => proc { o.time&.to_f } basic :time, value: proc { o.time&.to_f }
basic :timestamp, :value => proc { o.timestamp.to_f } basic :timestamp, value: proc { o.timestamp.to_f }
end end
+38 -38
View File
@@ -2,65 +2,65 @@ structure :message do
basic :id basic :id
basic :token basic :token
expansion(:status) { expansion(:status) do
{ {
:status => o.status, status: o.status,
:last_delivery_attempt => o.last_delivery_attempt ? o.last_delivery_attempt.to_f : nil, last_delivery_attempt: o.last_delivery_attempt ? o.last_delivery_attempt.to_f : nil,
:held => o.held == 1 ? true : false, held: o.held == 1,
:hold_expiry => o.hold_expiry ? o.hold_expiry.to_f : nil hold_expiry: o.hold_expiry ? o.hold_expiry.to_f : nil
} }
} end
expansion(:details) { expansion(:details) do
{ {
:rcpt_to => o.rcpt_to, rcpt_to: o.rcpt_to,
:mail_from => o.mail_from, mail_from: o.mail_from,
:subject => o.subject, subject: o.subject,
:message_id => o.message_id, message_id: o.message_id,
:timestamp => o.timestamp.to_f, timestamp: o.timestamp.to_f,
:direction => o.scope, direction: o.scope,
:size => o.size, size: o.size,
:bounce => o.bounce, bounce: o.bounce,
:bounce_for_id => o.bounce_for_id, bounce_for_id: o.bounce_for_id,
:tag => o.tag, tag: o.tag,
:received_with_ssl => o.received_with_ssl received_with_ssl: o.received_with_ssl
} }
} end
expansion(:inspection) { expansion(:inspection) do
{ {
:inspected => o.inspected == 1 ? true : false, inspected: o.inspected == 1,
:spam => o.spam == 1 ? true : false, spam: o.spam == 1,
:spam_score => o.spam_score.to_f, spam_score: o.spam_score.to_f,
:threat => o.threat == 1 ? true : false, threat: o.threat == 1,
:threat_details => o.threat_details threat_details: o.threat_details
} }
} end
expansion(:plain_body) { o.plain_body } expansion(:plain_body) { o.plain_body }
expansion(:html_body) { o.html_body } expansion(:html_body) { o.html_body }
expansion(:attachments) { expansion(:attachments) do
o.attachments.map do |attachment| o.attachments.map do |attachment|
{ {
:filename => attachment.filename.to_s, filename: attachment.filename.to_s,
:content_type => attachment.mime_type, content_type: attachment.mime_type,
:data => Base64.encode64(attachment.body.to_s), data: Base64.encode64(attachment.body.to_s),
:size => attachment.body.to_s.bytesize, size: attachment.body.to_s.bytesize,
:hash => Digest::SHA1.hexdigest(attachment.body.to_s) hash: Digest::SHA1.hexdigest(attachment.body.to_s)
} }
end end
} end
expansion(:headers) { o.headers } expansion(:headers) { o.headers }
expansion(:raw_message) { Base64.encode64(o.raw_message) } expansion(:raw_message) { Base64.encode64(o.raw_message) }
expansion(:activity_entries) { expansion(:activity_entries) do
{ {
:loads => o.loads, loads: o.loads,
:clicks => o.clicks clicks: o.clicks
} }
} end
end end
@@ -19,7 +19,7 @@ class AddressEndpointsController < ApplicationController
flash[:notice] = params[:return_notice] if params[:return_notice].present? flash[:notice] = params[:return_notice] if params[:return_notice].present?
redirect_to_with_json [:return_to, [organization, @server, :address_endpoints]] redirect_to_with_json [:return_to, [organization, @server, :address_endpoints]]
else else
render_form_errors 'new', @address_endpoint render_form_errors "new", @address_endpoint
end end
end end
@@ -27,7 +27,7 @@ class AddressEndpointsController < ApplicationController
if @address_endpoint.update(safe_params) if @address_endpoint.update(safe_params)
redirect_to_with_json [organization, @server, :address_endpoints] redirect_to_with_json [organization, @server, :address_endpoints]
else else
render_form_errors 'edit', @address_endpoint render_form_errors "edit", @address_endpoint
end end
end end
+20 -20
View File
@@ -1,4 +1,4 @@
require 'authie/session' require "authie/session"
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
@@ -7,37 +7,37 @@ class ApplicationController < ActionController::Base
before_action :login_required before_action :login_required
before_action :set_timezone before_action :set_timezone
rescue_from Authie::Session::InactiveSession, :with => :auth_session_error rescue_from Authie::Session::InactiveSession, with: :auth_session_error
rescue_from Authie::Session::ExpiredSession, :with => :auth_session_error rescue_from Authie::Session::ExpiredSession, with: :auth_session_error
rescue_from Authie::Session::BrowserMismatch, :with => :auth_session_error rescue_from Authie::Session::BrowserMismatch, with: :auth_session_error
private private
def login_required def login_required
unless logged_in? return if logged_in?
redirect_to login_path(:return_to => request.fullpath)
end redirect_to login_path(return_to: request.fullpath)
end end
def admin_required def admin_required
if logged_in? if logged_in?
unless current_user.admin? unless current_user.admin?
render :plain => "Not permitted" render plain: "Not permitted"
end end
else else
redirect_to login_path(:return_to => request.fullpath) redirect_to login_path(return_to: request.fullpath)
end end
end end
def require_organization_owner def require_organization_owner
unless organization.owner == current_user return if organization.owner == current_user
redirect_to organization_root_path(organization), :alert => "This page can only be accessed by the organization's owner (#{organization.owner.name})"
end redirect_to organization_root_path(organization), alert: "This page can only be accessed by the organization's owner (#{organization.owner.name})"
end end
def auth_session_error(exception) def auth_session_error(exception)
Rails.logger.info "AuthSessionError: #{exception.class}: #{exception.message}" Rails.logger.info "AuthSessionError: #{exception.class}: #{exception.message}"
redirect_to login_path(:return_to => request.fullpath) redirect_to login_path(return_to: request.fullpath)
end end
def page_title def page_title
@@ -46,7 +46,7 @@ class ApplicationController < ActionController::Base
helper_method :page_title helper_method :page_title
def redirect_to_with_return_to(url, *args) def redirect_to_with_return_to(url, *args)
if params[:return_to].blank? || !params[:return_to].starts_with?('/') if params[:return_to].blank? || !params[:return_to].starts_with?("/")
redirect_to url_with_return_to(url), *args redirect_to url_with_return_to(url), *args
else else
redirect_to url_with_return_to(url), *args redirect_to url_with_return_to(url), *args
@@ -54,7 +54,7 @@ class ApplicationController < ActionController::Base
end end
def set_timezone def set_timezone
Time.zone = logged_in? ? current_user.time_zone : 'UTC' Time.zone = logged_in? ? current_user.time_zone : "UTC"
end end
def append_info_to_payload(payload) def append_info_to_payload(payload)
@@ -64,7 +64,7 @@ class ApplicationController < ActionController::Base
end end
def url_with_return_to(url) def url_with_return_to(url)
if params[:return_to].blank? || !params[:return_to].starts_with?('/') if params[:return_to].blank? || !params[:return_to].starts_with?("/")
url_for(url) url_for(url)
else else
params[:return_to] params[:return_to]
@@ -83,14 +83,14 @@ class ApplicationController < ActionController::Base
end end
respond_to do |wants| respond_to do |wants|
wants.html { redirect_to url } wants.html { redirect_to url }
wants.json { render :json => {:redirect_to => url} } wants.json { render json: { redirect_to: url } }
end end
end end
def render_form_errors(action_name, object) def render_form_errors(action_name, object)
respond_to do |wants| respond_to do |wants|
wants.html { render action_name } wants.html { render action_name }
wants.json { render :json => {:form_errors => object.errors.full_messages}, :status => 422 } wants.json { render json: { form_errors: object.errors.full_messages }, status: :unprocessable_entity }
end end
end end
@@ -102,7 +102,7 @@ class ApplicationController < ActionController::Base
render options[:render_action] render options[:render_action]
end end
end end
wants.json { render :json => {:flash => {type => message}} } wants.json { render json: { flash: { type => message } } }
end end
end end
@@ -111,7 +111,7 @@ class ApplicationController < ActionController::Base
auth_session.invalidate! auth_session.invalidate!
reset_session reset_session
end end
Authie::Session.start(self, :user => user) Authie::Session.start(self, user: user)
@current_user = user @current_user = user
end end
+2 -2
View File
@@ -18,7 +18,7 @@ class CredentialsController < ApplicationController
if @credential.save if @credential.save
redirect_to_with_json [organization, @server, :credentials] redirect_to_with_json [organization, @server, :credentials]
else else
render_form_errors 'new', @credential render_form_errors "new", @credential
end end
end end
@@ -26,7 +26,7 @@ class CredentialsController < ApplicationController
if @credential.update(params.require(:credential).permit(:name, :key, :hold)) if @credential.update(params.require(:credential).permit(:name, :key, :hold))
redirect_to_with_json [organization, @server, :credentials] redirect_to_with_json [organization, @server, :credentials]
else else
render_form_errors 'edit', @credential render_form_errors "edit", @credential
end end
end end
+35 -34
View File
@@ -28,7 +28,7 @@ class DomainsController < ApplicationController
@domain = scope.build(params.require(:domain).permit(:name, :verification_method)) @domain = scope.build(params.require(:domain).permit(:name, :verification_method))
if current_user.admin? if current_user.admin?
@domain.verification_method = 'DNS' @domain.verification_method = "DNS"
@domain.verified_at = Time.now @domain.verified_at = Time.now
end end
@@ -39,7 +39,7 @@ class DomainsController < ApplicationController
redirect_to_with_json [:verify, organization, @server, @domain] redirect_to_with_json [:verify, organization, @server, @domain]
end end
else else
render_form_errors 'new', @domain render_form_errors "new", @domain
end end
end end
@@ -50,56 +50,57 @@ class DomainsController < ApplicationController
def verify def verify
if @domain.verified? if @domain.verified?
redirect_to [organization, @server, :domains], :alert => "#{@domain.name} has already been verified." redirect_to [organization, @server, :domains], alert: "#{@domain.name} has already been verified."
return return
end end
if request.post? return unless request.post?
case @domain.verification_method
when 'DNS' case @domain.verification_method
if @domain.verify_with_dns when "DNS"
redirect_to_with_json [:setup, organization, @server, @domain], :notice => "#{@domain.name} has been verified successfully. You now need to configure your DNS records." if @domain.verify_with_dns
redirect_to_with_json [:setup, organization, @server, @domain], notice: "#{@domain.name} has been verified successfully. You now need to configure your DNS records."
else
respond_to do |wants|
wants.html { flash.now[:alert] = "We couldn't verify your domain. Please double check you've added the TXT record correctly." }
wants.json { render json: { flash: { alert: "We couldn't verify your domain. Please double check you've added the TXT record correctly." } } }
end
end
when "Email"
if params[:code]
if @domain.verification_token == params[:code].to_s.strip
@domain.verify
redirect_to_with_json [:setup, organization, @server, @domain], notice: "#{@domain.name} has been verified successfully. You now need to configure your DNS records."
else else
respond_to do |wants| respond_to do |wants|
wants.html { flash.now[:alert] = "We couldn't verify your domain. Please double check you've added the TXT record correctly." } wants.html { flash.now[:alert] = "Invalid verification code. Please check and try again." }
wants.json { render :json => {:flash => {:alert => "We couldn't verify your domain. Please double check you've added the TXT record correctly."}}} wants.json { render json: { flash: { alert: "Invalid verification code. Please check and try again." } } }
end end
end end
when 'Email' elsif params[:email_address].present?
if params[:code] raise Postal::Error, "Invalid email address" unless @domain.verification_email_addresses.include?(params[:email_address])
if @domain.verification_token == params[:code].to_s.strip
@domain.verify AppMailer.verify_domain(@domain, params[:email_address], current_user).deliver
redirect_to_with_json [:setup, organization, @server, @domain], :notice => "#{@domain.name} has been verified successfully. You now need to configure your DNS records." if @domain.owner.is_a?(Server)
else redirect_to_with_json verify_organization_server_domain_path(organization, @server, @domain, email_address: params[:email_address])
respond_to do |wants| else
wants.html { flash.now[:alert] = "Invalid verification code. Please check and try again." } redirect_to_with_json verify_organization_domain_path(organization, @domain, email_address: params[:email_address])
wants.json { render :json => {:flash => {:alert => "Invalid verification code. Please check and try again."}}}
end
end
elsif params[:email_address].present?
raise Postal::Error, "Invalid email address" unless @domain.verification_email_addresses.include?(params[:email_address])
AppMailer.verify_domain(@domain, params[:email_address], current_user).deliver
if @domain.owner.is_a?(Server)
redirect_to_with_json verify_organization_server_domain_path(organization, @server, @domain, :email_address => params[:email_address])
else
redirect_to_with_json verify_organization_domain_path(organization, @domain, :email_address => params[:email_address])
end
end end
end end
end end
end end
def setup def setup
unless @domain.verified? return if @domain.verified?
redirect_to [:verify, organization, @server, @domain], :alert => "You can't set up DNS for this domain until it has been verified."
end redirect_to [:verify, organization, @server, @domain], alert: "You can't set up DNS for this domain until it has been verified."
end end
def check def check
if @domain.check_dns(:manual) if @domain.check_dns(:manual)
redirect_to_with_json [organization, @server, :domains], :notice => "Your DNS records for #{@domain.name} look good!" redirect_to_with_json [organization, @server, :domains], notice: "Your DNS records for #{@domain.name} look good!"
else else
redirect_to_with_json [:setup, organization, @server, @domain], :alert => "There seems to be something wrong with your DNS records. Check below for information." redirect_to_with_json [:setup, organization, @server, @domain], alert: "There seems to be something wrong with your DNS records. Check below for information."
end end
end end
+2 -2
View File
@@ -19,7 +19,7 @@ class HTTPEndpointsController < ApplicationController
flash[:notice] = params[:return_notice] if params[:return_notice].present? flash[:notice] = params[:return_notice] if params[:return_notice].present?
redirect_to_with_json [:return_to, [organization, @server, :http_endpoints]] redirect_to_with_json [:return_to, [organization, @server, :http_endpoints]]
else else
render_form_errors 'new', @http_endpoint render_form_errors "new", @http_endpoint
end end
end end
@@ -27,7 +27,7 @@ class HTTPEndpointsController < ApplicationController
if @http_endpoint.update(safe_params) if @http_endpoint.update(safe_params)
redirect_to_with_json [organization, @server, :http_endpoints] redirect_to_with_json [organization, @server, :http_endpoints]
else else
render_form_errors 'edit', @http_endpoint render_form_errors "edit", @http_endpoint
end end
end end
+2 -2
View File
@@ -13,7 +13,7 @@ class IPAddressesController < ApplicationController
if @ip_address.save if @ip_address.save
redirect_to_with_json [:edit, @ip_pool] redirect_to_with_json [:edit, @ip_pool]
else else
render_form_errors 'new', @ip_address render_form_errors "new", @ip_address
end end
end end
@@ -21,7 +21,7 @@ class IPAddressesController < ApplicationController
if @ip_address.update(safe_params) if @ip_address.update(safe_params)
redirect_to_with_json [:edit, @ip_pool] redirect_to_with_json [:edit, @ip_pool]
else else
render_form_errors 'edit', @ip_address render_form_errors "edit", @ip_address
end end
end end
+2 -2
View File
@@ -29,7 +29,7 @@ class IPPoolRulesController < ApplicationController
if @ip_pool_rule.save if @ip_pool_rule.save
redirect_to_with_json [organization, @server, :ip_pool_rules] redirect_to_with_json [organization, @server, :ip_pool_rules]
else else
render_form_errors 'new', @ip_pool_rule render_form_errors "new", @ip_pool_rule
end end
end end
@@ -37,7 +37,7 @@ class IPPoolRulesController < ApplicationController
if @ip_pool_rule.update(safe_params) if @ip_pool_rule.update(safe_params)
redirect_to_with_json [organization, @server, :ip_pool_rules] redirect_to_with_json [organization, @server, :ip_pool_rules]
else else
render_form_errors 'edit', @ip_pool_rule render_form_errors "edit", @ip_pool_rule
end end
end end
+6 -6
View File
@@ -14,25 +14,25 @@ class IPPoolsController < ApplicationController
def create def create
@ip_pool = IPPool.new(safe_params) @ip_pool = IPPool.new(safe_params)
if @ip_pool.save if @ip_pool.save
redirect_to_with_json [:edit, @ip_pool], :notice => "IP Pool has been added successfully. You can now add IP addresses to it." redirect_to_with_json [:edit, @ip_pool], notice: "IP Pool has been added successfully. You can now add IP addresses to it."
else else
render_form_errors 'new', @ip_pool render_form_errors "new", @ip_pool
end end
end end
def update def update
if @ip_pool.update(safe_params) if @ip_pool.update(safe_params)
redirect_to_with_json [:edit, @ip_pool], :notice => "IP Pool has been updated." redirect_to_with_json [:edit, @ip_pool], notice: "IP Pool has been updated."
else else
render_form_errors 'edit', @ip_pool render_form_errors "edit", @ip_pool
end end
end end
def destroy def destroy
@ip_pool.destroy @ip_pool.destroy
redirect_to_with_json :ip_pools, :notice => "IP pool has been removed successfully." redirect_to_with_json :ip_pools, notice: "IP pool has been removed successfully."
rescue ActiveRecord::DeleteRestrictionError => e rescue ActiveRecord::DeleteRestrictionError => e
redirect_to_with_json [:edit, @ip_pool], :alert => "IP pool cannot be removed because it still has associated addresses or servers." redirect_to_with_json [:edit, @ip_pool], alert: "IP pool cannot be removed because it still has associated addresses or servers."
end end
private private
+51 -49
View File
@@ -1,4 +1,4 @@
class MessagesController < ApplicationController class MessagesController < ApplicationController
include WithinOrganization include WithinOrganization
@@ -6,12 +6,12 @@
before_action { params[:id] && @message = @server.message_db.message(params[:id].to_i) } before_action { params[:id] && @message = @server.message_db.message(params[:id].to_i) }
def new def new
if params[:direction] == 'incoming' if params[:direction] == "incoming"
@message = IncomingMessagePrototype.new(@server, request.ip, 'web-ui', {}) @message = IncomingMessagePrototype.new(@server, request.ip, "web-ui", {})
@message.from = session[:test_in_from] || current_user.email_tag @message.from = session[:test_in_from] || current_user.email_tag
@message.to = @server.routes.order(:name).first&.description @message.to = @server.routes.order(:name).first&.description
else else
@message = OutgoingMessagePrototype.new(@server, request.ip, 'web-ui', {}) @message = OutgoingMessagePrototype.new(@server, request.ip, "web-ui", {})
@message.to = session[:test_out_to] || current_user.email_address @message.to = session[:test_out_to] || current_user.email_address
if domain = @server.domains.verified.order(:name).first if domain = @server.domains.verified.order(:name).first
@message.from = "test@#{domain.name}" @message.from = "test@#{domain.name}"
@@ -22,28 +22,28 @@
end end
def create def create
if params[:direction] == 'incoming' if params[:direction] == "incoming"
session[:test_in_from] = params[:message][:from] if params[:message] session[:test_in_from] = params[:message][:from] if params[:message]
@message = IncomingMessagePrototype.new(@server, request.ip, 'web-ui', params[:message]) @message = IncomingMessagePrototype.new(@server, request.ip, "web-ui", params[:message])
@message.attachments = [{:name => "test.txt", :content_type => "text/plain", :data => "Hello world!"}] @message.attachments = [{ name: "test.txt", content_type: "text/plain", data: "Hello world!" }]
else else
session[:test_out_to] = params[:message][:to] if params[:message] session[:test_out_to] = params[:message][:to] if params[:message]
@message = OutgoingMessagePrototype.new(@server, request.ip, 'web-ui', params[:message]) @message = OutgoingMessagePrototype.new(@server, request.ip, "web-ui", params[:message])
end end
if result = @message.create_messages if result = @message.create_messages
if result.size == 1 if result.size == 1
redirect_to_with_json organization_server_message_path(organization, @server, result.first.last[:id]), :notice => "Message was queued successfully" redirect_to_with_json organization_server_message_path(organization, @server, result.first.last[:id]), notice: "Message was queued successfully"
else else
redirect_to_with_json [:queue, organization, @server], :notice => "Messages queued successfully " redirect_to_with_json [:queue, organization, @server], notice: "Messages queued successfully "
end end
else else
respond_to do |wants| respond_to do |wants|
wants.html do wants.html do
flash.now[:alert] = "Your message could not be sent. Ensure that all fields are completed fully. #{result.errors.inspect}" flash.now[:alert] = "Your message could not be sent. Ensure that all fields are completed fully. #{result.errors.inspect}"
render 'new' render "new"
end end
wants.json do wants.json do
render :json => {:flash => {:alert => "Your message could not be sent. Please check all field are completed fully."}} render json: { flash: { alert: "Your message could not be sent. Please check all field are completed fully." } }
end end
end end
@@ -52,58 +52,62 @@
def outgoing def outgoing
@searchable = true @searchable = true
get_messages('outgoing') get_messages("outgoing")
respond_to do |wants| respond_to do |wants|
wants.html wants.html
wants.json { render :json => { wants.json do
:flash => flash.each_with_object({}) { |(type, message), hash| hash[type] = message}, render json: {
:region_html => render_to_string(:partial => 'index', :formats => [:html]) flash: flash.each_with_object({}) { |(type, message), hash| hash[type] = message },
}} region_html: render_to_string(partial: "index", formats: [:html])
}
end
end end
end end
def incoming def incoming
@searchable = true @searchable = true
get_messages('incoming') get_messages("incoming")
respond_to do |wants| respond_to do |wants|
wants.html wants.html
wants.json { render :json => { wants.json do
:flash => flash.each_with_object({}) { |(type, message), hash| hash[type] = message}, render json: {
:region_html => render_to_string(:partial => 'index', :formats => [:html]) flash: flash.each_with_object({}) { |(type, message), hash| hash[type] = message },
}} region_html: render_to_string(partial: "index", formats: [:html])
}
end
end end
end end
def held def held
get_messages('held') get_messages("held")
end end
def deliveries def deliveries
render :json => {:html => render_to_string(:partial => 'deliveries', :locals => {:message => @message})} render json: { html: render_to_string(partial: "deliveries", locals: { message: @message }) }
end end
def html_raw def html_raw
render :html => @message.html_body_without_tracking_image.html_safe render html: @message.html_body_without_tracking_image.html_safe
end end
def spam_checks def spam_checks
@spam_checks = @message.spam_checks.sort_by { |s| s['score']}.reverse @spam_checks = @message.spam_checks.sort_by { |s| s["score"] }.reverse
end end
def attachment def attachment
if @message.attachments.size > params[:attachment].to_i if @message.attachments.size > params[:attachment].to_i
attachment = @message.attachments[params[:attachment].to_i] attachment = @message.attachments[params[:attachment].to_i]
send_data attachment.body, :content_type => attachment.mime_type, :disposition => 'download', :filename => attachment.filename send_data attachment.body, content_type: attachment.mime_type, disposition: "download", filename: attachment.filename
else else
redirect_to attachments_organization_server_message_path(organization, @server, @message.id), :alert => "Attachment not found. Choose an attachment from the list below." redirect_to attachments_organization_server_message_path(organization, @server, @message.id), alert: "Attachment not found. Choose an attachment from the list below."
end end
end end
def download def download
if @message.raw_message if @message.raw_message
send_data @message.raw_message, :filename => "Message-#{organization.permalink}-#{@server.permalink}-#{@message.id}.eml", :content_type => "text/plain" send_data @message.raw_message, filename: "Message-#{organization.permalink}-#{@server.permalink}-#{@message.id}.eml", content_type: "text/plain"
else else
redirect_to organization_server_message_path(organization, @server, @message.id), :alert => "We no longer have the raw message stored for this message." redirect_to organization_server_message_path(organization, @server, @message.id), alert: "We no longer have the raw message stored for this message."
end end
end end
@@ -113,10 +117,10 @@
@message.queued_message.queue! @message.queued_message.queue!
flash[:notice] = "This message will be retried shortly." flash[:notice] = "This message will be retried shortly."
elsif @message.held? elsif @message.held?
@message.add_to_message_queue(:manual => true) @message.add_to_message_queue(manual: true)
flash[:notice] = "This message has been released. Delivery will be attempted shortly." flash[:notice] = "This message has been released. Delivery will be attempted shortly."
else else
@message.add_to_message_queue(:manual => true) @message.add_to_message_queue(manual: true)
flash[:notice] = "This message will be redelivered shortly." flash[:notice] = "This message will be redelivered shortly."
end end
else else
@@ -148,10 +152,10 @@
private private
def get_messages(scope) def get_messages(scope)
if scope == 'held' if scope == "held"
options = {:where => {:held => 1}} options = { where: { held: 1 } }
else else
options = {:where => {:scope => scope, :spam => false}, :order => :timestamp, :direction => 'desc'} options = { where: { scope: scope, spam: false }, order: :timestamp, direction: "desc" }
if @query = (params[:query] || session["msg_query_#{@server.id}_#{scope}"]).presence if @query = (params[:query] || session["msg_query_#{@server.id}_#{scope}"]).presence
session["msg_query_#{@server.id}_#{scope}"] = @query session["msg_query_#{@server.id}_#{scope}"] = @query
@@ -160,8 +164,8 @@
flash.now[:alert] = "It doesn't appear you entered anything to filter on. Please double check your query." flash.now[:alert] = "It doesn't appear you entered anything to filter on. Please double check your query."
else else
@queried = true @queried = true
if qs[:order] == 'oldest-first' if qs[:order] == "oldest-first"
options[:direction] = 'asc' options[:direction] = "asc"
end end
options[:where][:rcpt_to] = qs[:to] if qs[:to] options[:where][:rcpt_to] = qs[:to] if qs[:to]
@@ -176,7 +180,7 @@
end end
options[:where][:tag] = qs[:tag] if qs[:tag] options[:where][:tag] = qs[:tag] if qs[:tag]
options[:where][:id] = qs[:id] if qs[:id] options[:where][:id] = qs[:id] if qs[:id]
options[:where][:spam] = true if qs[:spam] == 'yes' || qs[:spam] == 'y' options[:where][:spam] = true if qs[:spam] == "yes" || qs[:spam] == "y"
if qs[:before] || qs[:after] if qs[:before] || qs[:after]
options[:where][:timestamp] = {} options[:where][:timestamp] = {}
if qs[:before] if qs[:before]
@@ -208,21 +212,19 @@
def get_time_from_string(string) def get_time_from_string(string)
begin begin
if string =~ /\A(\d{2,4})\-(\d{2})\-(\d{2}) (\d{2})\:(\d{2})\z/ if string =~ /\A(\d{2,4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})\z/
time = Time.new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i) time = Time.new(::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i, ::Regexp.last_match(4).to_i, ::Regexp.last_match(5).to_i)
elsif string =~ /\A(\d{2,4})\-(\d{2})\-(\d{2})\z/ elsif string =~ /\A(\d{2,4})-(\d{2})-(\d{2})\z/
time = Time.new($1.to_i, $2.to_i, $3.to_i, 0) time = Time.new(::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i, 0)
else else
time = Chronic.parse(string, :context => :past) time = Chronic.parse(string, context: :past)
end end
rescue rescue StandardError
end end
if time.nil? raise TimeUndetermined, "Couldn't determine a suitable time from '#{string}'" if time.nil?
raise TimeUndetermined, "Couldn't determine a suitable time from '#{string}'"
else time
time
end
end end
end end
@@ -1,7 +1,7 @@
class OrganizationIPPoolsController < ApplicationController class OrganizationIPPoolsController < ApplicationController
include WithinOrganization include WithinOrganization
before_action :admin_required, :only => [:assignments] before_action :admin_required, only: [:assignments]
def index def index
@ip_pools = organization.ip_pools.order(:name) @ip_pools = organization.ip_pools.order(:name)
@@ -10,7 +10,7 @@ class OrganizationIPPoolsController < ApplicationController
def assignments def assignments
organization.ip_pool_ids = params[:ip_pools] organization.ip_pool_ids = params[:ip_pools]
organization.save! organization.save!
redirect_to [organization, :ip_pools], :notice => "Organization IP pools have been updated successfully" redirect_to [organization, :ip_pools], notice: "Organization IP pools have been updated successfully"
end end
end end
+14 -14
View File
@@ -1,6 +1,6 @@
class OrganizationsController < ApplicationController class OrganizationsController < ApplicationController
before_action :admin_required, :only => [:new, :create, :delete, :destroy] before_action :admin_required, only: [:new, :create, :delete, :destroy]
def index def index
if current_user.admin? if current_user.admin?
@@ -17,48 +17,48 @@ class OrganizationsController < ApplicationController
@organization = Organization.new @organization = Organization.new
end end
def edit
@organization_obj = current_user.organizations_scope.find(organization.id)
end
def create def create
@organization = Organization.new(params.require(:organization).permit(:name, :permalink)) @organization = Organization.new(params.require(:organization).permit(:name, :permalink))
@organization.owner = current_user @organization.owner = current_user
if @organization.save if @organization.save
redirect_to_with_json organization_root_path(@organization) redirect_to_with_json organization_root_path(@organization)
else else
render_form_errors 'new', @organization render_form_errors "new", @organization
end end
end end
def edit
@organization_obj = current_user.organizations_scope.find(organization.id)
end
def update def update
@organization_obj = current_user.organizations_scope.find(organization.id) @organization_obj = current_user.organizations_scope.find(organization.id)
if @organization_obj.update(params.require(:organization).permit(:name, :time_zone)) if @organization_obj.update(params.require(:organization).permit(:name, :time_zone))
redirect_to_with_json organization_settings_path(@organization_obj), :notice => "Settings for #{@organization_obj.name} have been saved successfully." redirect_to_with_json organization_settings_path(@organization_obj), notice: "Settings for #{@organization_obj.name} have been saved successfully."
else else
render_form_errors 'edit', @organization_obj render_form_errors "edit", @organization_obj
end end
end end
def destroy def destroy
unless current_user.authenticate(params[:password]) unless current_user.authenticate(params[:password])
respond_to do |wants| respond_to do |wants|
wants.html { redirect_to organization_delete_path(@organization), :alert => "The password you entered was not valid. Please check and try again." } wants.html { redirect_to organization_delete_path(@organization), alert: "The password you entered was not valid. Please check and try again." }
wants.json { render :json => {:alert => "The password you entered was invalid. Please check and try again."} } wants.json { render json: { alert: "The password you entered was invalid. Please check and try again." } }
end end
return return
end end
organization.soft_destroy organization.soft_destroy
redirect_to_with_json root_path(:nrd => 1), :notice => "#{@organization.name} has been removed successfully." redirect_to_with_json root_path(nrd: 1), notice: "#{@organization.name} has been removed successfully."
end end
private private
def organization def organization
if [:edit, :update, :delete, :destroy].include?(action_name.to_sym) return unless [:edit, :update, :delete, :destroy].include?(action_name.to_sym)
@organization ||= params[:org_permalink] ? current_user.organizations_scope.find_by_permalink!(params[:org_permalink]) : nil
end @organization ||= params[:org_permalink] ? current_user.organizations_scope.find_by_permalink!(params[:org_permalink]) : nil
end end
helper_method :organization helper_method :organization
+3 -3
View File
@@ -18,7 +18,7 @@ class RoutesController < ApplicationController
if @route.save if @route.save
redirect_to_with_json [organization, @server, :routes] redirect_to_with_json [organization, @server, :routes]
else else
render_form_errors 'new', @route render_form_errors "new", @route
end end
end end
@@ -26,7 +26,7 @@ class RoutesController < ApplicationController
if @route.update(safe_params) if @route.update(safe_params)
redirect_to_with_json [organization, @server, :routes] redirect_to_with_json [organization, @server, :routes]
else else
render_form_errors 'edit', @route render_form_errors "edit", @route
end end
end end
@@ -38,7 +38,7 @@ class RoutesController < ApplicationController
private private
def safe_params def safe_params
params.require(:route).permit(:name, :domain_id, :spam_mode, :_endpoint, :additional_route_endpoints_array => []) params.require(:route).permit(:name, :domain_id, :spam_mode, :_endpoint, additional_route_endpoints_array: [])
end end
end end
+11 -11
View File
@@ -2,7 +2,7 @@ class ServersController < ApplicationController
include WithinOrganization include WithinOrganization
before_action :admin_required, :only => [:advanced, :suspend, :unsuspend] before_action :admin_required, only: [:advanced, :suspend, :unsuspend]
before_action { params[:id] && @server = organization.servers.present.find_by_permalink!(params[:id]) } before_action { params[:id] && @server = organization.servers.present.find_by_permalink!(params[:id]) }
def index def index
@@ -23,7 +23,7 @@ class ServersController < ApplicationController
@first_date = graph_data.first.first @first_date = graph_data.first.first
@last_date = graph_data.last.first @last_date = graph_data.last.first
@graph_data = graph_data.map(&:last) @graph_data = graph_data.map(&:last)
@messages = @server.message_db.messages(:order => 'id', :direction => 'desc', :limit => 6) @messages = @server.message_db.messages(order: "id", direction: "desc", limit: 6)
end end
def new def new
@@ -35,7 +35,7 @@ class ServersController < ApplicationController
if @server.save if @server.save
redirect_to_with_json organization_server_path(organization, @server) redirect_to_with_json organization_server_path(organization, @server)
else else
render_form_errors 'new', @server render_form_errors "new", @server
end end
end end
@@ -43,9 +43,9 @@ class ServersController < ApplicationController
extra_params = [:spam_threshold, :spam_failure_threshold, :postmaster_address] extra_params = [:spam_threshold, :spam_failure_threshold, :postmaster_address]
extra_params += [:send_limit, :allow_sender, :log_smtp_data, :outbound_spam_threshold, :message_retention_days, :raw_message_retention_days, :raw_message_retention_size] if current_user.admin? extra_params += [:send_limit, :allow_sender, :log_smtp_data, :outbound_spam_threshold, :message_retention_days, :raw_message_retention_days, :raw_message_retention_size] if current_user.admin?
if @server.update(safe_params(*extra_params)) if @server.update(safe_params(*extra_params))
redirect_to_with_json organization_server_path(organization, @server), :notice => "Server settings have been updated" redirect_to_with_json organization_server_path(organization, @server), notice: "Server settings have been updated"
else else
render_form_errors 'edit', @server render_form_errors "edit", @server
end end
end end
@@ -53,31 +53,31 @@ class ServersController < ApplicationController
unless current_user.authenticate(params[:password]) unless current_user.authenticate(params[:password])
respond_to do |wants| respond_to do |wants|
wants.html do wants.html do
redirect_to [:delete, organization, @server], :alert => "The password you entered was not valid. Please check and try again." redirect_to [:delete, organization, @server], alert: "The password you entered was not valid. Please check and try again."
end end
wants.json do wants.json do
render :json => {:alert => "The password you entere was invalid. Please check and try again"} render json: { alert: "The password you entere was invalid. Please check and try again" }
end end
end end
return return
end end
@server.soft_destroy @server.soft_destroy
redirect_to_with_json organization_root_path(organization), :notice => "#{@server.name} has been deleted successfully" redirect_to_with_json organization_root_path(organization), notice: "#{@server.name} has been deleted successfully"
end end
def queue def queue
@messages = @server.queued_messages.order(:id => :desc).page(params[:page]) @messages = @server.queued_messages.order(id: :desc).page(params[:page])
@messages_with_message = @messages.include_message @messages_with_message = @messages.include_message
end end
def suspend def suspend
@server.suspend(params[:reason]) @server.suspend(params[:reason])
redirect_to_with_json [organization, @server], :notice => "Server has been suspended" redirect_to_with_json [organization, @server], notice: "Server has been suspended"
end end
def unsuspend def unsuspend
@server.unsuspend @server.unsuspend
redirect_to_with_json [organization, @server], :notice => "Server has been unsuspended" redirect_to_with_json [organization, @server], notice: "Server has been unsuspended"
end end
private private
+28 -28
View File
@@ -1,8 +1,8 @@
class SessionsController < ApplicationController class SessionsController < ApplicationController
layout 'sub' layout "sub"
skip_before_action :login_required, :only => [:new, :create, :create_with_token, :begin_password_reset, :finish_password_reset, :ip, :raise_error] skip_before_action :login_required, only: [:new, :create, :create_with_token, :begin_password_reset, :finish_password_reset, :ip, :raise_error]
def create def create
login(User.authenticate(params[:email_address], params[:password])) login(User.authenticate(params[:email_address], params[:password]))
@@ -10,13 +10,13 @@ class SessionsController < ApplicationController
redirect_to_with_return_to root_path redirect_to_with_return_to root_path
rescue Postal::Errors::AuthenticationError => e rescue Postal::Errors::AuthenticationError => e
flash.now[:alert] = "The credentials you've provided are incorrect. Please check and try again." flash.now[:alert] = "The credentials you've provided are incorrect. Please check and try again."
render 'new' render "new"
end end
def create_with_token def create_with_token
result = JWT.decode(params[:token], Postal.signing_key.to_s, 'HS256')[0] result = JWT.decode(params[:token], Postal.signing_key.to_s, "HS256")[0]
if result['timestamp'] > 1.minute.ago.to_f if result["timestamp"] > 1.minute.ago.to_f
login(User.find(result['user'].to_i)) login(User.find(result["user"].to_i))
redirect_to root_path redirect_to root_path
else else
destroy destroy
@@ -33,42 +33,42 @@ class SessionsController < ApplicationController
def persist def persist
auth_session.persist! if logged_in? auth_session.persist! if logged_in?
render :plain => "OK" render plain: "OK"
end end
def begin_password_reset def begin_password_reset
if request.post? return unless request.post?
if user = User.where(:email_address => params[:email_address]).first
user.begin_password_reset(params[:return_to]) if user = User.where(email_address: params[:email_address]).first
redirect_to login_path(:return_to => params[:return_to]), :notice => "Please check your e-mail and click the link in the e-mail we've sent you." user.begin_password_reset(params[:return_to])
else redirect_to login_path(return_to: params[:return_to]), notice: "Please check your e-mail and click the link in the e-mail we've sent you."
redirect_to login_reset_path(:return_to => params[:return_to]), :alert => "No user exists with that e-mail address. Please check and try again." else
end redirect_to login_reset_path(return_to: params[:return_to]), alert: "No user exists with that e-mail address. Please check and try again."
end end
end end
def finish_password_reset def finish_password_reset
@user = User.where(:password_reset_token => params[:token]).where("password_reset_token_valid_until > ?", Time.now).first @user = User.where(password_reset_token: params[:token]).where("password_reset_token_valid_until > ?", Time.now).first
if @user.nil? if @user.nil?
redirect_to login_path(:return_to => params[:return_to]), :alert => "This link has expired or never existed. Please choose reset password to try again." redirect_to login_path(return_to: params[:return_to]), alert: "This link has expired or never existed. Please choose reset password to try again."
end end
if request.post? return unless request.post?
if params[:password].blank?
flash.now[:alert] = "You must enter a new password" if params[:password].blank?
return flash.now[:alert] = "You must enter a new password"
end return
@user.password = params[:password]
@user.password_confirmation = params[:password_confirmation]
if @user.save
login(@user)
redirect_to_with_return_to root_path, :notice => "Your new password has been set and you've been logged in."
end
end end
@user.password = params[:password]
@user.password_confirmation = params[:password_confirmation]
return unless @user.save
login(@user)
redirect_to_with_return_to root_path, notice: "Your new password has been set and you've been logged in."
end end
def ip def ip
render :plain => "ip: #{request.ip} remote ip: #{request.remote_ip}" render plain: "ip: #{request.ip} remote ip: #{request.remote_ip}"
end end
end end
+3 -2
View File
@@ -1,4 +1,5 @@
class SMTPEndpointsController < ApplicationController class SMTPEndpointsController < ApplicationController
include WithinOrganization include WithinOrganization
before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) } before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) }
before_action { params[:id] && @smtp_endpoint = @server.smtp_endpoints.find_by_uuid!(params[:id]) } before_action { params[:id] && @smtp_endpoint = @server.smtp_endpoints.find_by_uuid!(params[:id]) }
@@ -17,7 +18,7 @@ class SMTPEndpointsController < ApplicationController
flash[:notice] = params[:return_notice] if params[:return_notice].present? flash[:notice] = params[:return_notice] if params[:return_notice].present?
redirect_to_with_json [:return_to, [organization, @server, :smtp_endpoints]] redirect_to_with_json [:return_to, [organization, @server, :smtp_endpoints]]
else else
render_form_errors 'new', @smtp_endpoint render_form_errors "new", @smtp_endpoint
end end
end end
@@ -25,7 +26,7 @@ class SMTPEndpointsController < ApplicationController
if @smtp_endpoint.update(safe_params) if @smtp_endpoint.update(safe_params)
redirect_to_with_json [organization, @server, :smtp_endpoints] redirect_to_with_json [organization, @server, :smtp_endpoints]
else else
render_form_errors 'edit', @smtp_endpoint render_form_errors "edit", @smtp_endpoint
end end
end end
+7 -6
View File
@@ -1,4 +1,5 @@
class TrackDomainsController < ApplicationController class TrackDomainsController < ApplicationController
include WithinOrganization include WithinOrganization
before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) } before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) }
before_action { params[:id] && @track_domain = @server.track_domains.find_by_uuid!(params[:id]) } before_action { params[:id] && @track_domain = @server.track_domains.find_by_uuid!(params[:id]) }
@@ -16,7 +17,7 @@ class TrackDomainsController < ApplicationController
if @track_domain.save if @track_domain.save
redirect_to_with_json [:return_to, [organization, @server, :track_domains]] redirect_to_with_json [:return_to, [organization, @server, :track_domains]]
else else
render_form_errors 'new', @track_domain render_form_errors "new", @track_domain
end end
end end
@@ -24,7 +25,7 @@ class TrackDomainsController < ApplicationController
if @track_domain.update(params.require(:track_domain).permit(:track_loads, :track_clicks, :excluded_click_domains, :ssl_enabled)) if @track_domain.update(params.require(:track_domain).permit(:track_loads, :track_clicks, :excluded_click_domains, :ssl_enabled))
redirect_to_with_json [organization, @server, :track_domains] redirect_to_with_json [organization, @server, :track_domains]
else else
render_form_errors 'edit', @track_domain render_form_errors "edit", @track_domain
end end
end end
@@ -35,15 +36,15 @@ class TrackDomainsController < ApplicationController
def check def check
if @track_domain.check_dns if @track_domain.check_dns
redirect_to_with_json [organization, @server, :track_domains], :notice => "Your CNAME for #{@track_domain.full_name} looks good!" redirect_to_with_json [organization, @server, :track_domains], notice: "Your CNAME for #{@track_domain.full_name} looks good!"
else else
redirect_to_with_json [organization, @server, :track_domains], :alert => "There seems to be something wrong with your DNS record. Check documentation for information." redirect_to_with_json [organization, @server, :track_domains], alert: "There seems to be something wrong with your DNS record. Check documentation for information."
end end
end end
def toggle_ssl def toggle_ssl
@track_domain.update(:ssl_enabled => !@track_domain.ssl_enabled) @track_domain.update(ssl_enabled: !@track_domain.ssl_enabled)
redirect_to_with_json [organization, @server, :track_domains], :notice => "SSL settings for #{@track_domain.full_name} updated successfully." redirect_to_with_json [organization, @server, :track_domains], notice: "SSL settings for #{@track_domain.full_name} updated successfully."
end end
end end
+25 -25
View File
@@ -1,16 +1,20 @@
class UserController < ApplicationController class UserController < ApplicationController
skip_before_action :login_required, :only => [:new, :create, :join] skip_before_action :login_required, only: [:new, :create, :join]
def new def new
@user_invite = UserInvite.active.find_by!(:uuid => params[:invite_token]) @user_invite = UserInvite.active.find_by!(uuid: params[:invite_token])
@user = User.new @user = User.new
@user.email_address = @user_invite.email_address @user.email_address = @user_invite.email_address
render :layout => 'sub' render layout: "sub"
end
def edit
@user = User.find(current_user.id)
end end
def create def create
@user_invite = UserInvite.active.find_by!(:uuid => params[:invite_token]) @user_invite = UserInvite.active.find_by!(uuid: params[:invite_token])
@user = User.new(params.require(:user).permit(:first_name, :last_name, :email_address, :password, :password_confirmation)) @user = User.new(params.require(:user).permit(:first_name, :last_name, :email_address, :password, :password_confirmation))
@user.email_verified_at = Time.now @user.email_verified_at = Time.now
if @user.save if @user.save
@@ -18,19 +22,19 @@ class UserController < ApplicationController
self.current_user = @user self.current_user = @user
redirect_to root_path redirect_to root_path
else else
render 'new', :layout => 'sub' render "new", layout: "sub"
end end
end end
def join def join
if @invite = UserInvite.where(:uuid => params[:token]).where("expires_at > ?", Time.now).first if @invite = UserInvite.where(uuid: params[:token]).where("expires_at > ?", Time.now).first
if logged_in? if logged_in?
if request.post? if request.post?
@invite.accept(current_user) @invite.accept(current_user)
redirect_to_with_json root_path(:nrd => 1), :notice => "Invitation has been accepted successfully. You now have access to this organization." redirect_to_with_json root_path(nrd: 1), notice: "Invitation has been accepted successfully. You now have access to this organization."
elsif request.delete? elsif request.delete?
@invite.reject @invite.reject
redirect_to_with_json root_path(:nrd => 1), :notice => "Invitation has been rejected successfully." redirect_to_with_json root_path(nrd: 1), notice: "Invitation has been rejected successfully."
else else
@organizations = @invite.organizations.order(:name).to_a @organizations = @invite.organizations.order(:name).to_a
end end
@@ -38,14 +42,10 @@ class UserController < ApplicationController
redirect_to new_signup_path(params[:token]) redirect_to new_signup_path(params[:token])
end end
else else
redirect_to_with_json root_path(:nrd => 1), :alert => "The invite URL you have has expired. Please ask the person who invited you to re-send your invitation." redirect_to_with_json root_path(nrd: 1), alert: "The invite URL you have has expired. Please ask the person who invited you to re-send your invitation."
end end
end end
def edit
@user = User.find(current_user.id)
end
def update def update
@user = User.find(current_user.id) @user = User.find(current_user.id)
@user.attributes = params.require(:user).permit(:first_name, :last_name, :time_zone, :email_address, :password, :password_confirmation) @user.attributes = params.require(:user).permit(:first_name, :last_name, :time_zone, :email_address, :password, :password_confirmation)
@@ -56,10 +56,10 @@ class UserController < ApplicationController
respond_to do |wants| respond_to do |wants|
wants.html do wants.html do
flash.now[:alert] = "The current password you have entered is incorrect. Please check and try again." flash.now[:alert] = "The current password you have entered is incorrect. Please check and try again."
render 'edit' render "edit"
end end
wants.json do wants.json do
render :json => {:alert => "The current password you've entered is incorrect. Please check and try again"} render json: { alert: "The current password you've entered is incorrect. Please check and try again" }
end end
end end
return return
@@ -69,23 +69,23 @@ class UserController < ApplicationController
if @user.save if @user.save
if email_changed if email_changed
redirect_to_with_json verify_path(:return_to => settings_path), :notice => "Your settings have been updated successfully. As you've changed, your e-mail address you'll need to verify it before you can continue." redirect_to_with_json verify_path(return_to: settings_path), notice: "Your settings have been updated successfully. As you've changed, your e-mail address you'll need to verify it before you can continue."
else else
redirect_to_with_json settings_path, :notice => "Your settings have been updated successfully." redirect_to_with_json settings_path, notice: "Your settings have been updated successfully."
end end
else else
render_form_errors 'edit', @user render_form_errors "edit", @user
end end
end end
def verify def verify
if request.post? return unless request.post?
if params[:code].to_s.strip == current_user.email_verification_token.to_s || (Rails.env.development? && params[:code].to_s.strip == "123456")
current_user.verify! if params[:code].to_s.strip == current_user.email_verification_token.to_s || (Rails.env.development? && params[:code].to_s.strip == "123456")
redirect_to_with_json [:return_to, root_path], :notice => "Thanks - your e-mail address has been verified successfully." current_user.verify!
else redirect_to_with_json [:return_to, root_path], notice: "Thanks - your e-mail address has been verified successfully."
flash_now :alert, "The code you've entered isn't correct. Please check and try again." else
end flash_now :alert, "The code you've entered isn't correct. Please check and try again."
end end
end end
+10 -11
View File
@@ -11,45 +11,44 @@ class UsersController < ApplicationController
@user = User.new(admin: true) @user = User.new(admin: true)
end end
def edit
end
def create def create
@user = User.new(params.require(:user).permit(:email_address, :first_name, :last_name, :password, :password_confirmation, :admin, organization_ids: [])) @user = User.new(params.require(:user).permit(:email_address, :first_name, :last_name, :password, :password_confirmation, :admin, organization_ids: []))
if @user.save if @user.save
redirect_to_with_json :users, :notice => "#{@user.name} has been created successfully." redirect_to_with_json :users, notice: "#{@user.name} has been created successfully."
else else
render_form_errors 'new', @user render_form_errors "new", @user
end end
end end
def edit
end
def update def update
@user.attributes = params.require(:user).permit(:email_address, :first_name, :last_name, :admin, organization_ids: []) @user.attributes = params.require(:user).permit(:email_address, :first_name, :last_name, :admin, organization_ids: [])
if @user == current_user && !@user.admin? if @user == current_user && !@user.admin?
respond_to do |wants| respond_to do |wants|
wants.html { redirect_to users_path, alert: "You cannot change your own admin status" } wants.html { redirect_to users_path, alert: "You cannot change your own admin status" }
wants.json { render :json => {:form_errors => ["You cannot change your own admin status"]}, :status => 422 } wants.json { render json: { form_errors: ["You cannot change your own admin status"] }, status: :unprocessable_entity }
end end
return return
end end
if @user.save if @user.save
redirect_to_with_json :users, :notice => "Permissions for #{@user.name} have been updated successfully." redirect_to_with_json :users, notice: "Permissions for #{@user.name} have been updated successfully."
else else
render_form_errors 'edit', @user render_form_errors "edit", @user
end end
end end
def destroy def destroy
if @user == current_user if @user == current_user
redirect_to_with_json :users, :alert => "You cannot delete your own user." redirect_to_with_json :users, alert: "You cannot delete your own user."
return return
end end
@user.destroy! @user.destroy!
redirect_to_with_json :users, :notice => "#{@user.name} has been removed" redirect_to_with_json :users, notice: "#{@user.name} has been removed"
end end
end end
+5 -4
View File
@@ -1,4 +1,5 @@
class WebhooksController < ApplicationController class WebhooksController < ApplicationController
include WithinOrganization include WithinOrganization
before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) } before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) }
before_action { params[:id] && @webhook = @server.webhooks.find_by_uuid!(params[:id]) } before_action { params[:id] && @webhook = @server.webhooks.find_by_uuid!(params[:id]) }
@@ -8,7 +9,7 @@ class WebhooksController < ApplicationController
end end
def new def new
@webhook = @server.webhooks.build(:all_events => true) @webhook = @server.webhooks.build(all_events: true)
end end
def create def create
@@ -16,7 +17,7 @@ class WebhooksController < ApplicationController
if @webhook.save if @webhook.save
redirect_to_with_json [organization, @server, :webhooks] redirect_to_with_json [organization, @server, :webhooks]
else else
render_form_errors 'new', @webhook render_form_errors "new", @webhook
end end
end end
@@ -24,7 +25,7 @@ class WebhooksController < ApplicationController
if @webhook.update(safe_params) if @webhook.update(safe_params)
redirect_to_with_json [organization, @server, :webhooks] redirect_to_with_json [organization, @server, :webhooks]
else else
render_form_errors 'edit', @webhook render_form_errors "edit", @webhook
end end
end end
@@ -45,7 +46,7 @@ class WebhooksController < ApplicationController
private private
def safe_params def safe_params
params.require(:webhook).permit(:name, :url, :all_events, :enabled, :events => []) params.require(:webhook).permit(:name, :url, :all_events, :enabled, events: [])
end end
end end
+11 -13
View File
@@ -1,9 +1,9 @@
module ApplicationHelper module ApplicationHelper
def format_delivery_details(server, text) def format_delivery_details(server, text)
text.gsub!(/\<msg\:(\d+)\>/) do text.gsub!(/<msg:(\d+)>/) do
id = $1.to_i id = ::Regexp.last_match(1).to_i
link_to("message ##{id}", organization_server_message_path(server.organization, server, id), :class => "u-link") link_to("message ##{id}", organization_server_message_path(server.organization, server, id), class: "u-link")
end end
text.html_safe text.html_safe
end end
@@ -29,7 +29,7 @@ module ApplicationHelper
unless server_domains.empty? unless server_domains.empty?
s << "<optgroup label='Server Domains'>" s << "<optgroup label='Server Domains'>"
for domain in server_domains for domain in server_domains
selected = domain == selected_domain ? "selected='selected'" : '' selected = domain == selected_domain ? "selected='selected'" : ""
s << "<option value='#{domain.id}' #{selected}>#{domain.name}</option>" s << "<option value='#{domain.id}' #{selected}>#{domain.name}</option>"
end end
s << "</optgroup>" s << "</optgroup>"
@@ -39,12 +39,11 @@ module ApplicationHelper
unless organization_domains.empty? unless organization_domains.empty?
s << "<optgroup label='Organization Domains'>" s << "<optgroup label='Organization Domains'>"
for domain in organization_domains for domain in organization_domains
selected = domain == selected_domain ? "selected='selected'" : '' selected = domain == selected_domain ? "selected='selected'" : ""
s << "<option value='#{domain.id}' #{selected}>#{domain.name}</option>" s << "<option value='#{domain.id}' #{selected}>#{domain.name}</option>"
end end
s << "</optgroup>" s << "</optgroup>"
end end
end.html_safe end.html_safe
end end
@@ -57,19 +56,18 @@ module ApplicationHelper
s << "<optgroup label='HTTP Endpoints'>" s << "<optgroup label='HTTP Endpoints'>"
for endpoint in http_endpoints for endpoint in http_endpoints
value = "#{endpoint.class}##{endpoint.uuid}" value = "#{endpoint.class}##{endpoint.uuid}"
selected = value == selected_value ? "selected='selected'" : '' selected = value == selected_value ? "selected='selected'" : ""
s << "<option value='#{value}' #{selected}>#{endpoint.description}</option>" s << "<option value='#{value}' #{selected}>#{endpoint.description}</option>"
end end
s << "</optgroup>" s << "</optgroup>"
end end
smtp_endpoints = server.smtp_endpoints.order(:name).to_a smtp_endpoints = server.smtp_endpoints.order(:name).to_a
if smtp_endpoints.present? if smtp_endpoints.present?
s << "<optgroup label='SMTP Endpoints'>" s << "<optgroup label='SMTP Endpoints'>"
for endpoint in smtp_endpoints for endpoint in smtp_endpoints
value = "#{endpoint.class}##{endpoint.uuid}" value = "#{endpoint.class}##{endpoint.uuid}"
selected = value == selected_value ? "selected='selected'" : '' selected = value == selected_value ? "selected='selected'" : ""
s << "<option value='#{value}' #{selected}>#{endpoint.description}</option>" s << "<option value='#{value}' #{selected}>#{endpoint.description}</option>"
end end
s << "</optgroup>" s << "</optgroup>"
@@ -80,7 +78,7 @@ module ApplicationHelper
s << "<optgroup label='Address Endpoints'>" s << "<optgroup label='Address Endpoints'>"
for endpoint in address_endpoints for endpoint in address_endpoints
value = "#{endpoint.class}##{endpoint.uuid}" value = "#{endpoint.class}##{endpoint.uuid}"
selected = value == selected_value ? "selected='selected'" : '' selected = value == selected_value ? "selected='selected'" : ""
s << "<option value='#{value}' #{selected}>#{endpoint.address}</option>" s << "<option value='#{value}' #{selected}>#{endpoint.address}</option>"
end end
s << "</optgroup>" s << "</optgroup>"
@@ -89,14 +87,14 @@ module ApplicationHelper
unless options[:other] == false unless options[:other] == false
s << "<optgroup label='Other Options'>" s << "<optgroup label='Other Options'>"
Route::MODES.each do |mode| Route::MODES.each do |mode|
next if mode == 'Endpoint' next if mode == "Endpoint"
selected = (selected_value == mode ? "selected='selected'" : '')
selected = (selected_value == mode ? "selected='selected'" : "")
text = t("route_modes.#{mode.underscore}") text = t("route_modes.#{mode.underscore}")
s << "<option value='#{mode}' #{selected}>#{text}</option>" s << "<option value='#{mode}' #{selected}>#{text}</option>"
end end
s << "</optgroup>" s << "</optgroup>"
end end
end.html_safe end.html_safe
end end
+3 -1
View File
@@ -1,6 +1,7 @@
class ActionDeletionJob < Postal::Job class ActionDeletionJob < Postal::Job
def perform def perform
object = params['type'].constantize.deleted.find_by_id(params['id']) object = params["type"].constantize.deleted.find_by_id(params["id"])
if object if object
log "Deleting #{params['type']}##{params['id']}" log "Deleting #{params['type']}##{params['id']}"
object.destroy object.destroy
@@ -9,4 +10,5 @@ class ActionDeletionJob < Postal::Job
log "Couldn't find deleted object #{params['type']}##{params['id']}" log "Couldn't find deleted object #{params['type']}##{params['id']}"
end end
end end
end end
+2
View File
@@ -1,4 +1,5 @@
class ActionDeletionsJob < Postal::Job class ActionDeletionsJob < Postal::Job
def perform def perform
Organization.deleted.each do |org| Organization.deleted.each do |org|
log "Permanently removing organization #{org.id} (#{org.permalink})" log "Permanently removing organization #{org.id} (#{org.permalink})"
@@ -10,4 +11,5 @@ class ActionDeletionsJob < Postal::Job
server.destroy server.destroy
end end
end end
end end
+3 -1
View File
@@ -1,6 +1,7 @@
class CheckAllDNSJob < Postal::Job class CheckAllDNSJob < Postal::Job
def perform def perform
Domain.where.not(:dns_checked_at => nil).where("dns_checked_at <= ?", 1.hour.ago).each do |domain| Domain.where.not(dns_checked_at: nil).where("dns_checked_at <= ?", 1.hour.ago).each do |domain|
log "Checking DNS for domain: #{domain.name}" log "Checking DNS for domain: #{domain.name}"
domain.check_dns(:auto) domain.check_dns(:auto)
end end
@@ -10,4 +11,5 @@ class CheckAllDNSJob < Postal::Job
domain.check_dns domain.check_dns
end end
end end
end end
+3 -1
View File
@@ -1,7 +1,9 @@
require 'authie/session' require "authie/session"
class CleanupAuthieSessionsJob < Postal::Job class CleanupAuthieSessionsJob < Postal::Job
def perform def perform
Authie::Session.cleanup Authie::Session.cleanup
end end
end end
+5 -3
View File
@@ -1,12 +1,14 @@
class ExpireHeldMessagesJob < Postal::Job class ExpireHeldMessagesJob < Postal::Job
def perform def perform
Server.all.each do |server| Server.all.each do |server|
messages = server.message_db.messages(:where => { messages = server.message_db.messages(where: {
:status => 'Held', status: "Held",
:hold_expiry => {:less_than => Time.now.to_f} hold_expiry: { less_than: Time.now.to_f }
}) })
messages.each(&:cancel_hold) messages.each(&:cancel_hold)
end end
end end
end end
@@ -1,4 +1,5 @@
class ProcessMessageRetentionJob < Postal::Job class ProcessMessageRetentionJob < Postal::Job
def perform def perform
Server.all.each do |server| Server.all.each do |server|
if server.raw_message_retention_days if server.raw_message_retention_days
@@ -18,4 +19,5 @@ class ProcessMessageRetentionJob < Postal::Job
end end
end end
end end
end end
+2
View File
@@ -1,8 +1,10 @@
class PruneSuppressionListsJob < Postal::Job class PruneSuppressionListsJob < Postal::Job
def perform def perform
Server.all.each do |s| Server.all.each do |s|
log "Pruning suppression lists for server #{s.id}" log "Pruning suppression lists for server #{s.id}"
s.message_db.suppression_list.prune s.message_db.suppression_list.prune
end end
end end
end end
+2
View File
@@ -1,8 +1,10 @@
class PruneWebhookRequestsJob < Postal::Job class PruneWebhookRequestsJob < Postal::Job
def perform def perform
Server.all.each do |s| Server.all.each do |s|
log "Pruning webhook requests for server #{s.id}" log "Pruning webhook requests for server #{s.id}"
s.message_db.webhooks.prune s.message_db.webhooks.prune
end end
end end
end end
+2
View File
@@ -1,5 +1,7 @@
class RequeueWebhooksJob < Postal::Job class RequeueWebhooksJob < Postal::Job
def perform def perform
WebhookRequest.requeue_all WebhookRequest.requeue_all
end end
end end
+2
View File
@@ -1,5 +1,7 @@
class SendNotificationsJob < Postal::Job class SendNotificationsJob < Postal::Job
def perform def perform
Server.send_send_limit_notifications Server.send_send_limit_notifications
end end
end end
+11 -11
View File
@@ -1,25 +1,25 @@
class SendWebhookJob < Postal::Job class SendWebhookJob < Postal::Job
def perform def perform
if server = Server.find(params['server_id']) if server = Server.find(params["server_id"])
new_items = {} new_items = {}
if params['payload'] if params["payload"]
for key, value in params['payload'] for key, value in params["payload"]
if key.to_s =~ /\A\_(\w+)/ next unless key.to_s =~ /\A_(\w+)/
begin
new_items[$1] = server.message_db.message(value.to_i).webhook_hash begin
rescue Postal::MessageDB::Message::NotFound new_items[::Regexp.last_match(1)] = server.message_db.message(value.to_i).webhook_hash
end rescue Postal::MessageDB::Message::NotFound
end end
end end
end end
new_items.each do |key, value| new_items.each do |key, value|
params['payload'].delete("_#{key}") params["payload"].delete("_#{key}")
params['payload'][key] = value params["payload"][key] = value
end end
WebhookRequest.trigger(server, params['event'], params['payload']) WebhookRequest.trigger(server, params["event"], params["payload"])
else else
log "Couldn't find server with ID #{params['server_id']}" log "Couldn't find server with ID #{params['server_id']}"
end end
+2
View File
@@ -1,5 +1,7 @@
class SleepJob < Postal::Job class SleepJob < Postal::Job
def perform def perform
sleep 5 sleep 5
end end
end end
-1
View File
@@ -1,7 +1,6 @@
class TidyRawMessagesJob < Postal::Job class TidyRawMessagesJob < Postal::Job
def perform def perform
end end
end end
+97 -99
View File
@@ -1,6 +1,7 @@
class UnqueueMessageJob < Postal::Job class UnqueueMessageJob < Postal::Job
def perform def perform
if original_message = QueuedMessage.find_by_id(params['id']) if original_message = QueuedMessage.find_by_id(params["id"])
if original_message.acquire_lock if original_message.acquire_lock
log "Lock acquired for queued message #{original_message.id}" log "Lock acquired for queued message #{original_message.id}"
@@ -22,7 +23,7 @@ class UnqueueMessageJob < Postal::Job
begin begin
other_messages = original_message.batchable_messages(100) other_messages = original_message.batchable_messages(100)
log "Found #{other_messages.size} associated messages to process at the same time (batch key: #{original_message.batch_key})" log "Found #{other_messages.size} associated messages to process at the same time (batch key: #{original_message.batch_key})"
rescue rescue StandardError
original_message.unlock original_message.unlock
raise raise
end end
@@ -45,7 +46,7 @@ class UnqueueMessageJob < Postal::Job
# #
if queued_message.server.suspended? if queued_message.server.suspended?
log "#{log_prefix} Server is suspended. Holding message." log "#{log_prefix} Server is suspended. Holding message."
queued_message.message.create_delivery('Held', :details => "Mail server has been suspended. No e-mails can be processed at present. Contact support for assistance.") queued_message.message.create_delivery("Held", details: "Mail server has been suspended. No e-mails can be processed at present. Contact support for assistance.")
queued_message.destroy queued_message.destroy
next next
end end
@@ -53,19 +54,19 @@ class UnqueueMessageJob < Postal::Job
# We might not be able to send this any more, check the attempts # We might not be able to send this any more, check the attempts
if queued_message.attempts >= Postal.config.general.maximum_delivery_attempts if queued_message.attempts >= Postal.config.general.maximum_delivery_attempts
details = "Maximum number of delivery attempts (#{queued_message.attempts}) has been reached." details = "Maximum number of delivery attempts (#{queued_message.attempts}) has been reached."
if queued_message.message.scope == 'incoming' if queued_message.message.scope == "incoming"
# Send bounces to incoming e-mails when they are hard failed # Send bounces to incoming e-mails when they are hard failed
if bounce_id = queued_message.send_bounce if bounce_id = queued_message.send_bounce
details += " Bounce sent to sender (see message <msg:#{bounce_id}>)" details += " Bounce sent to sender (see message <msg:#{bounce_id}>)"
end end
elsif queued_message.message.scope == 'outgoing' elsif queued_message.message.scope == "outgoing"
# Add the recipient to the suppression list # Add the recipient to the suppression list
if queued_message.server.message_db.suppression_list.add(:recipient, queued_message.message.rcpt_to, :reason => "too many soft fails") if queued_message.server.message_db.suppression_list.add(:recipient, queued_message.message.rcpt_to, reason: "too many soft fails")
log "Added #{queued_message.message.rcpt_to} to suppression list because maximum attempts has been reached" log "Added #{queued_message.message.rcpt_to} to suppression list because maximum attempts has been reached"
details += " Added #{queued_message.message.rcpt_to} to suppression list because delivery has failed #{queued_message.attempts} times." details += " Added #{queued_message.message.rcpt_to} to suppression list because delivery has failed #{queued_message.attempts} times."
end end
end end
queued_message.message.create_delivery('HardFail', :details => details) queued_message.message.create_delivery("HardFail", details: details)
queued_message.destroy queued_message.destroy
log "#{log_prefix} Message has reached maximum number of attempts. Hard failing." log "#{log_prefix} Message has reached maximum number of attempts. Hard failing."
next next
@@ -74,15 +75,15 @@ class UnqueueMessageJob < Postal::Job
# If the raw message has been removed (removed by retention) # If the raw message has been removed (removed by retention)
unless queued_message.message.raw_message? unless queued_message.message.raw_message?
log "#{log_prefix} Raw message has been removed. Not sending." log "#{log_prefix} Raw message has been removed. Not sending."
queued_message.message.create_delivery('HardFail', :details => "Raw message has been removed. Cannot send message.") queued_message.message.create_delivery("HardFail", details: "Raw message has been removed. Cannot send message.")
queued_message.destroy queued_message.destroy
next next
end end
# #
# Handle Incoming Messages #  Handle Incoming Messages
# #
if queued_message.message.scope == 'incoming' if queued_message.message.scope == "incoming"
# #
# If this is a bounce, we need to handle it as such # If this is a bounce, we need to handle it as such
# #
@@ -91,8 +92,8 @@ class UnqueueMessageJob < Postal::Job
original_messages = queued_message.message.original_messages original_messages = queued_message.message.original_messages
unless original_messages.empty? unless original_messages.empty?
for original_message in queued_message.message.original_messages for original_message in queued_message.message.original_messages
queued_message.message.update(:bounce_for_id => original_message.id, :domain_id => original_message.domain_id) queued_message.message.update(bounce_for_id: original_message.id, domain_id: original_message.domain_id)
queued_message.message.create_delivery('Processed', :details => "This has been detected as a bounce message for <msg:#{original_message.id}>.") queued_message.message.create_delivery("Processed", details: "This has been detected as a bounce message for <msg:#{original_message.id}>.")
original_message.bounce!(queued_message.message) original_message.bounce!(queued_message.message)
log "#{log_prefix} Bounce linked with message #{original_message.id}" log "#{log_prefix} Bounce linked with message #{original_message.id}"
end end
@@ -101,11 +102,11 @@ class UnqueueMessageJob < Postal::Job
end end
# This message was sent to the return path but hasn't been matched # This message was sent to the return path but hasn't been matched
# to an original message. If we have a route for this, route it #  to an original message. If we have a route for this, route it
# otherwise we'll drop at this point. #  otherwise we'll drop at this point.
if queued_message.message.route_id.nil? if queued_message.message.route_id.nil?
log "#{log_prefix} No source messages found. Hard failing." log "#{log_prefix} No source messages found. Hard failing."
queued_message.message.create_delivery('HardFail', :details => "This message was a bounce but we couldn't link it with any outgoing message and there was no route for it.") queued_message.message.create_delivery("HardFail", details: "This message was a bounce but we couldn't link it with any outgoing message and there was no route for it.")
queued_message.destroy queued_message.destroy
next next
end end
@@ -124,7 +125,7 @@ class UnqueueMessageJob < Postal::Job
queued_message.message.inspect_message queued_message.message.inspect_message
if queued_message.message.inspected == 1 if queued_message.message.inspected == 1
is_spam = queued_message.message.spam_score > queued_message.server.spam_threshold is_spam = queued_message.message.spam_score > queued_message.server.spam_threshold
queued_message.message.update(:spam => 1) if is_spam queued_message.message.update(spam: 1) if is_spam
queued_message.message.append_headers( queued_message.message.append_headers(
"X-Postal-Spam: #{queued_message.message.spam == 1 ? 'yes' : 'no'}", "X-Postal-Spam: #{queued_message.message.spam == 1 ? 'yes' : 'no'}",
"X-Postal-Spam-Threshold: #{queued_message.server.spam_threshold}", "X-Postal-Spam-Threshold: #{queued_message.server.spam_threshold}",
@@ -140,15 +141,15 @@ class UnqueueMessageJob < Postal::Job
# #
if queued_message.message.spam_score >= queued_message.server.spam_failure_threshold if queued_message.message.spam_score >= queued_message.server.spam_failure_threshold
log "#{log_prefix} Message has a spam score higher than the server's maxmimum. Hard failing." log "#{log_prefix} Message has a spam score higher than the server's maxmimum. Hard failing."
queued_message.message.create_delivery('HardFail', :details => "Message's spam score is higher than the failure threshold for this server. Threshold is currently #{queued_message.server.spam_failure_threshold}.") queued_message.message.create_delivery("HardFail", details: "Message's spam score is higher than the failure threshold for this server. Threshold is currently #{queued_message.server.spam_failure_threshold}.")
queued_message.destroy queued_message.destroy
next next
end end
# If the server is in development mode, hold it # If the server is in development mode, hold it
if queued_message.server.mode == 'Development' && !queued_message.manual? if queued_message.server.mode == "Development" && !queued_message.manual?
log "Server is in development mode so holding." log "Server is in development mode so holding."
queued_message.message.create_delivery('Held', :details => "Server is in development mode.") queued_message.message.create_delivery("Held", details: "Server is in development mode.")
queued_message.destroy queued_message.destroy
log "#{log_prefix} Server is in development mode. Holding." log "#{log_prefix} Server is in development mode. Holding."
next next
@@ -161,16 +162,16 @@ class UnqueueMessageJob < Postal::Job
if route = queued_message.message.route if route = queued_message.message.route
# If the route says we're holding quananteed mail and this is spam, we'll hold this # If the route says we're holding quananteed mail and this is spam, we'll hold this
if route.spam_mode == 'Quarantine' && queued_message.message.spam == 1 && !queued_message.manual? if route.spam_mode == "Quarantine" && queued_message.message.spam == 1 && !queued_message.manual?
queued_message.message.create_delivery('Held', :details => "Message placed into quarantine.") queued_message.message.create_delivery("Held", details: "Message placed into quarantine.")
queued_message.destroy queued_message.destroy
log "#{log_prefix} Route says to quarantine spam message. Holding." log "#{log_prefix} Route says to quarantine spam message. Holding."
next next
end end
# If the route says we're holding quananteed mail and this is spam, we'll hold this # If the route says we're holding quananteed mail and this is spam, we'll hold this
if route.spam_mode == 'Fail' && queued_message.message.spam == 1 && !queued_message.manual? if route.spam_mode == "Fail" && queued_message.message.spam == 1 && !queued_message.manual?
queued_message.message.create_delivery('HardFail', :details => "Message is spam and the route specifies it should be failed.") queued_message.message.create_delivery("HardFail", details: "Message is spam and the route specifies it should be failed.")
queued_message.destroy queued_message.destroy
log "#{log_prefix} Route says to fail spam message. Hard failing." log "#{log_prefix} Route says to fail spam message. Hard failing."
next next
@@ -179,8 +180,8 @@ class UnqueueMessageJob < Postal::Job
# #
# Messages that should be blindly accepted are blindly accepted # Messages that should be blindly accepted are blindly accepted
# #
if route.mode == 'Accept' if route.mode == "Accept"
queued_message.message.create_delivery('Processed', :details => "Message has been accepted but not sent to any endpoints.") queued_message.message.create_delivery("Processed", details: "Message has been accepted but not sent to any endpoints.")
queued_message.destroy queued_message.destroy
log "#{log_prefix} Route says to accept without endpoint. Marking as processed." log "#{log_prefix} Route says to accept without endpoint. Marking as processed."
next next
@@ -189,14 +190,14 @@ class UnqueueMessageJob < Postal::Job
# #
# Messages that should be accepted and held should be held # Messages that should be accepted and held should be held
# #
if route.mode == 'Hold' if route.mode == "Hold"
log "#{log_prefix} Route says to hold message." log "#{log_prefix} Route says to hold message."
if queued_message.manual? if queued_message.manual?
log "#{log_prefix} Message was queued manually. Marking as processed." log "#{log_prefix} Message was queued manually. Marking as processed."
queued_message.message.create_delivery('Processed', :details => "Message has been processed.") queued_message.message.create_delivery("Processed", details: "Message has been processed.")
else else
log "#{log_prefix} Message was not queued manually. Holding." log "#{log_prefix} Message was not queued manually. Holding."
queued_message.message.create_delivery('Held', :details => "Message has been accepted but not sent to any endpoints.") queued_message.message.create_delivery("Held", details: "Message has been accepted but not sent to any endpoints.")
end end
queued_message.destroy queued_message.destroy
next next
@@ -205,45 +206,43 @@ class UnqueueMessageJob < Postal::Job
# #
# Messages that should be bounced should be bounced (or rejected if they got this far) # Messages that should be bounced should be bounced (or rejected if they got this far)
# #
if route.mode == 'Bounce' || route.mode == 'Reject' if route.mode == "Bounce" || route.mode == "Reject"
if id = queued_message.send_bounce if id = queued_message.send_bounce
queued_message.message.create_delivery('HardFail', :details => "Message has been bounced because the route asks for this. See message <msg:#{id}>") queued_message.message.create_delivery("HardFail", details: "Message has been bounced because the route asks for this. See message <msg:#{id}>")
log "#{log_prefix} Route says to bounce. Hard failing and sent bounce (#{id})." log "#{log_prefix} Route says to bounce. Hard failing and sent bounce (#{id})."
end end
queued_message.destroy queued_message.destroy
next next
end end
begin if @fixed_result
if @fixed_result result = @fixed_result
result = @fixed_result else
case queued_message.message.endpoint
when SMTPEndpoint
sender = cached_sender(Postal::SMTPSender, queued_message.message.recipient_domain, nil, servers: [queued_message.message.endpoint])
when HTTPEndpoint
sender = cached_sender(Postal::HTTPSender, queued_message.message.endpoint)
when AddressEndpoint
sender = cached_sender(Postal::SMTPSender, queued_message.message.endpoint.domain, nil, force_rcpt_to: queued_message.message.endpoint.address)
else else
case queued_message.message.endpoint log "#{log_prefix} Invalid endpoint for route (#{queued_message.message.endpoint_type})"
when SMTPEndpoint queued_message.message.create_delivery("HardFail", details: "Invalid endpoint for route.")
sender = cached_sender(Postal::SMTPSender, queued_message.message.recipient_domain, nil, :servers => [queued_message.message.endpoint]) queued_message.destroy
when HTTPEndpoint next
sender = cached_sender(Postal::HTTPSender, queued_message.message.endpoint) end
when AddressEndpoint result = sender.send_message(queued_message.message)
sender = cached_sender(Postal::SMTPSender, queued_message.message.endpoint.domain, nil, :force_rcpt_to => queued_message.message.endpoint.address) if result.connect_error
else @fixed_result = result
log "#{log_prefix} Invalid endpoint for route (#{queued_message.message.endpoint_type})"
queued_message.message.create_delivery('HardFail', :details => "Invalid endpoint for route.")
queued_message.destroy
next
end
result = sender.send_message(queued_message.message)
if result.connect_error
@fixed_result = result
end
end end
end end
# Log the result # Log the result
log_details = result.details log_details = result.details
if result.type =='HardFail' && result.suppress_bounce if result.type == "HardFail" && result.suppress_bounce
# The delivery hard failed, but requested that no bounce be sent # The delivery hard failed, but requested that no bounce be sent
log "#{log_prefix} Suppressing bounce message after hard fail" log "#{log_prefix} Suppressing bounce message after hard fail"
elsif result.type =='HardFail' && queued_message.message.send_bounces? elsif result.type == "HardFail" && queued_message.message.send_bounces?
# If the message is a hard fail, send a bounce message for this message. # If the message is a hard fail, send a bounce message for this message.
log "#{log_prefix} Sending a bounce because message hard failed" log "#{log_prefix} Sending a bounce because message hard failed"
if bounce_id = queued_message.send_bounce if bounce_id = queued_message.send_bounce
@@ -252,7 +251,7 @@ class UnqueueMessageJob < Postal::Job
end end
end end
queued_message.message.create_delivery(result.type, :details => log_details, :output => result.output&.strip, :sent_with_ssl => result.secure, :log_id => result.log_id, :time => result.time) queued_message.message.create_delivery(result.type, details: log_details, output: result.output&.strip, sent_with_ssl: result.secure, log_id: result.log_id, time: result.time)
if result.retry if result.retry
log "#{log_prefix} Message requeued for trying later." log "#{log_prefix} Message requeued for trying later."
@@ -266,7 +265,7 @@ class UnqueueMessageJob < Postal::Job
end end
else else
log "#{log_prefix} No route and/or endpoint available for processing. Hard failing." log "#{log_prefix} No route and/or endpoint available for processing. Hard failing."
queued_message.message.create_delivery('HardFail', :details => "Message does not have a route and/or endpoint available for delivery.") queued_message.message.create_delivery("HardFail", details: "Message does not have a route and/or endpoint available for delivery.")
queued_message.destroy queued_message.destroy
next next
end end
@@ -275,10 +274,10 @@ class UnqueueMessageJob < Postal::Job
# #
# Handle Outgoing Messages # Handle Outgoing Messages
# #
if queued_message.message.scope == 'outgoing' if queued_message.message.scope == "outgoing"
if queued_message.message.domain.nil? if queued_message.message.domain.nil?
log "#{log_prefix} Message has no domain. Hard failing." log "#{log_prefix} Message has no domain. Hard failing."
queued_message.message.create_delivery('HardFail', :details => "Message's domain no longer exist") queued_message.message.create_delivery("HardFail", details: "Message's domain no longer exist")
queued_message.destroy queued_message.destroy
next next
end end
@@ -288,7 +287,7 @@ class UnqueueMessageJob < Postal::Job
# #
if queued_message.message.rcpt_to.blank? if queued_message.message.rcpt_to.blank?
log "#{log_prefix} Message has no to address. Hard failing." log "#{log_prefix} Message has no to address. Hard failing."
queued_message.message.create_delivery('HardFail', :details => "Message doesn't have an RCPT to") queued_message.message.create_delivery("HardFail", details: "Message doesn't have an RCPT to")
queued_message.destroy queued_message.destroy
next next
end end
@@ -298,7 +297,7 @@ class UnqueueMessageJob < Postal::Job
# #
if !queued_message.manual? && queued_message.message.credential && queued_message.message.credential.hold? if !queued_message.manual? && queued_message.message.credential && queued_message.message.credential.hold?
log "#{log_prefix} Credential wants us to hold messages. Holding." log "#{log_prefix} Credential wants us to hold messages. Holding."
queued_message.message.create_delivery('Held', :details => "Credential is configured to hold all messages authenticated by it.") queued_message.message.create_delivery("Held", details: "Credential is configured to hold all messages authenticated by it.")
queued_message.destroy queued_message.destroy
next next
end end
@@ -308,15 +307,15 @@ class UnqueueMessageJob < Postal::Job
# #
if !queued_message.manual? && sl = queued_message.server.message_db.suppression_list.get(:recipient, queued_message.message.rcpt_to) if !queued_message.manual? && sl = queued_message.server.message_db.suppression_list.get(:recipient, queued_message.message.rcpt_to)
log "#{log_prefix} Recipient is on the suppression list. Holding." log "#{log_prefix} Recipient is on the suppression list. Holding."
queued_message.message.create_delivery('Held', :details => "Recipient (#{queued_message.message.rcpt_to}) is on the suppression list (reason: #{sl['reason']})") queued_message.message.create_delivery("Held", details: "Recipient (#{queued_message.message.rcpt_to}) is on the suppression list (reason: #{sl['reason']})")
queued_message.destroy queued_message.destroy
next next
end end
# Extract a tag and add it to the message if one doesn't exist # Extract a tag and add it to the message if one doesn't exist
if queued_message.message.tag.nil? && tag = queued_message.message.headers['x-postal-tag'] if queued_message.message.tag.nil? && tag = queued_message.message.headers["x-postal-tag"]
log "#{log_prefix} Added tag #{tag.last}" log "#{log_prefix} Added tag #{tag.last}"
queued_message.message.update(:tag => tag.last) queued_message.message.update(tag: tag.last)
end end
# Parse the content of the message as appropriate # Parse the content of the message as appropriate
@@ -331,61 +330,60 @@ class UnqueueMessageJob < Postal::Job
queued_message.message.inspect_message queued_message.message.inspect_message
if queued_message.message.inspected == 1 if queued_message.message.inspected == 1
if queued_message.message.spam_score >= queued_message.server.outbound_spam_threshold if queued_message.message.spam_score >= queued_message.server.outbound_spam_threshold
queued_message.message.update(:spam => 1) queued_message.message.update(spam: 1)
end end
log "#{log_prefix} Message inspected successfully" log "#{log_prefix} Message inspected successfully"
end end
end end
if queued_message.message.spam == 1 if queued_message.message.spam == 1
queued_message.message.create_delivery("HardFail", :details => "Message is likely spam. Threshold is #{queued_message.server.outbound_spam_threshold} and the message scored #{queued_message.message.spam_score}.") queued_message.message.create_delivery("HardFail", details: "Message is likely spam. Threshold is #{queued_message.server.outbound_spam_threshold} and the message scored #{queued_message.message.spam_score}.")
queued_message.destroy queued_message.destroy
log "#{log_prefix} Message is spam (#{queued_message.message.spam_score}). Hard failing." log "#{log_prefix} Message is spam (#{queued_message.message.spam_score}). Hard failing."
next next
end end
# Add outgoing headers # Add outgoing headers
if !queued_message.message.has_outgoing_headers? unless queued_message.message.has_outgoing_headers?
queued_message.message.add_outgoing_headers queued_message.message.add_outgoing_headers
end end
# Check send limits # Check send limits
if queued_message.server.send_limit_exceeded? if queued_message.server.send_limit_exceeded?
# If we're over the limit, we're going to be holding this message # If we're over the limit, we're going to be holding this message
queued_message.server.update_columns(:send_limit_exceeded_at => Time.now, :send_limit_approaching_at => nil) queued_message.server.update_columns(send_limit_exceeded_at: Time.now, send_limit_approaching_at: nil)
queued_message.message.create_delivery('Held', :details => "Message held because send limit (#{queued_message.server.send_limit}) has been reached.") queued_message.message.create_delivery("Held", details: "Message held because send limit (#{queued_message.server.send_limit}) has been reached.")
queued_message.destroy queued_message.destroy
log "#{log_prefix} Server send limit has been exceeded. Holding." log "#{log_prefix} Server send limit has been exceeded. Holding."
next next
elsif queued_message.server.send_limit_approaching? elsif queued_message.server.send_limit_approaching?
# If we're approaching the limit, just say we are but continue to process the message # If we're approaching the limit, just say we are but continue to process the message
queued_message.server.update_columns(:send_limit_approaching_at => Time.now, :send_limit_exceeded_at => nil) queued_message.server.update_columns(send_limit_approaching_at: Time.now, send_limit_exceeded_at: nil)
else else
queued_message.server.update_columns(:send_limit_approaching_at => nil, :send_limit_exceeded_at => nil) queued_message.server.update_columns(send_limit_approaching_at: nil, send_limit_exceeded_at: nil)
end end
# Update the live stats for this message. # Update the live stats for this message.
queued_message.message.database.live_stats.increment(queued_message.message.scope) queued_message.message.database.live_stats.increment(queued_message.message.scope)
# If the server is in development mode, hold it # If the server is in development mode, hold it
if queued_message.server.mode == 'Development' && !queued_message.manual? if queued_message.server.mode == "Development" && !queued_message.manual?
log "Server is in development mode so holding." log "Server is in development mode so holding."
queued_message.message.create_delivery('Held', :details => "Server is in development mode.") queued_message.message.create_delivery("Held", details: "Server is in development mode.")
queued_message.destroy queued_message.destroy
log "#{log_prefix} Server is in development mode. Holding." log "#{log_prefix} Server is in development mode. Holding."
next next
end end
# Send the outgoing message to the SMTP sender # Send the outgoing message to the SMTP sender
begin
if @fixed_result if @fixed_result
result = @fixed_result result = @fixed_result
else else
sender = cached_sender(Postal::SMTPSender, queued_message.message.recipient_domain, queued_message.ip_address) sender = cached_sender(Postal::SMTPSender, queued_message.message.recipient_domain, queued_message.ip_address)
result = sender.send_message(queued_message.message) result = sender.send_message(queued_message.message)
if result.connect_error if result.connect_error
@fixed_result = result @fixed_result = result
end
end end
end end
@@ -393,30 +391,26 @@ class UnqueueMessageJob < Postal::Job
# If the message has been hard failed, check to see how many other recent hard fails we've had for the address # If the message has been hard failed, check to see how many other recent hard fails we've had for the address
# and if there are more than 2, suppress the address for 30 days. # and if there are more than 2, suppress the address for 30 days.
# #
if result.type == 'HardFail' if result.type == "HardFail"
recent_hard_fails = queued_message.server.message_db.select(:messages, :where => {:rcpt_to => queued_message.message.rcpt_to, :status => 'HardFail', :timestamp => {:greater_than => 24.hours.ago.to_f}}, :count => true) recent_hard_fails = queued_message.server.message_db.select(:messages, where: { rcpt_to: queued_message.message.rcpt_to, status: "HardFail", timestamp: { greater_than: 24.hours.ago.to_f } }, count: true)
if recent_hard_fails >= 1 if recent_hard_fails >= 1 && queued_message.server.message_db.suppression_list.add(:recipient, queued_message.message.rcpt_to, reason: "too many hard fails")
if queued_message.server.message_db.suppression_list.add(:recipient, queued_message.message.rcpt_to, :reason => "too many hard fails") log "#{log_prefix} Added #{queued_message.message.rcpt_to} to suppression list because #{recent_hard_fails} hard fails in 24 hours"
log "#{log_prefix} Added #{queued_message.message.rcpt_to} to suppression list because #{recent_hard_fails} hard fails in 24 hours" result.details += "." if result.details =~ /\.\z/
result.details += "." if result.details =~ /\.\z/ result.details += " Recipient added to suppression list (too many hard fails)."
result.details += " Recipient added to suppression list (too many hard fails)."
end
end end
end end
# #
# If a message is sent successfully, remove the users from the suppression list # If a message is sent successfully, remove the users from the suppression list
# #
if result.type == 'Sent' if result.type == "Sent" && queued_message.server.message_db.suppression_list.remove(:recipient, queued_message.message.rcpt_to)
if queued_message.server.message_db.suppression_list.remove(:recipient, queued_message.message.rcpt_to) log "#{log_prefix} Removed #{queued_message.message.rcpt_to} from suppression list because success"
log "#{log_prefix} Removed #{queued_message.message.rcpt_to} from suppression list because success" result.details += "." if result.details =~ /\.\z/
result.details += "." if result.details =~ /\.\z/ result.details += " Recipient removed from suppression list."
result.details += " Recipient removed from suppression list."
end
end end
# Log the result # Log the result
queued_message.message.create_delivery(result.type, :details => result.details, :output => result.output, :sent_with_ssl => result.secure, :log_id => result.log_id, :time => result.time) queued_message.message.create_delivery(result.type, details: result.details, output: result.output, sent_with_ssl: result.secure, log_id: result.log_id, time: result.time)
if result.retry if result.retry
log "#{log_prefix} Message requeued for trying later." log "#{log_prefix} Message requeued for trying later."
queued_message.retry_later(result.retry.is_a?(Integer) ? result.retry : nil) queued_message.retry_later(result.retry.is_a?(Integer) ? result.retry : nil)
@@ -425,17 +419,16 @@ class UnqueueMessageJob < Postal::Job
queued_message.destroy queued_message.destroy
end end
end end
rescue StandardError => e
rescue => e
log "#{log_prefix} Internal error: #{e.class}: #{e.message}" log "#{log_prefix} Internal error: #{e.class}: #{e.message}"
e.backtrace.each { |e| log("#{log_prefix} #{e}") } e.backtrace.each { |e| log("#{log_prefix} #{e}") }
queued_message.retry_later queued_message.retry_later
log "#{log_prefix} Queued message was unlocked" log "#{log_prefix} Queued message was unlocked"
if defined?(Raven) if defined?(Raven)
Raven.capture_exception(e, :extra => {:job_id => self.id, :server_id => queued_message.server_id, :message_id => queued_message.message_id}) Raven.capture_exception(e, extra: { job_id: self.id, server_id: queued_message.server_id, message_id: queued_message.message_id })
end end
if queued_message.message if queued_message.message
queued_message.message.create_delivery("Error", :details => "An internal error occurred while sending this message. This message will be retried automatically. If this persists, contact support for assistance.", :output => "#{e.class}: #{e.message}", :log_id => "J-#{self.id}") queued_message.message.create_delivery("Error", details: "An internal error occurred while sending this message. This message will be retried automatically. If this persists, contact support for assistance.", output: "#{e.class}: #{e.message}", log_id: "J-#{self.id}")
end end
end end
end end
@@ -447,7 +440,11 @@ class UnqueueMessageJob < Postal::Job
log "No queued message with ID #{params['id']} was available for processing." log "No queued message with ID #{params['id']} was available for processing."
end end
ensure ensure
@sender&.finish rescue nil begin
@sender&.finish
rescue StandardError
nil
end
end end
private private
@@ -459,4 +456,5 @@ class UnqueueMessageJob < Postal::Job
sender sender
end end
end end
end end
+3 -1
View File
@@ -1,6 +1,7 @@
class WebhookDeliveryJob < Postal::Job class WebhookDeliveryJob < Postal::Job
def perform def perform
if webhook_request = WebhookRequest.find_by_id(params['id']) if webhook_request = WebhookRequest.find_by_id(params["id"])
if webhook_request.deliver if webhook_request.deliver
log "Succesfully delivered" log "Succesfully delivered"
else else
@@ -10,4 +11,5 @@ class WebhookDeliveryJob < Postal::Job
log "No webhook request found with ID '#{params['id']}'" log "No webhook request found with ID '#{params['id']}'"
end end
end end
end end
+9 -9
View File
@@ -2,50 +2,50 @@ class AppMailer < ApplicationMailer
def verify_email_address(user) def verify_email_address(user)
@user = user @user = user
mail :to => @user.email_address, :subject => "Verify your new e-mail address" mail to: @user.email_address, subject: "Verify your new e-mail address"
end end
def new_user(user) def new_user(user)
@user = user @user = user
mail :to => @user.email_address, :subject => "Welcome to Postal" mail to: @user.email_address, subject: "Welcome to Postal"
end end
def user_invite(user_invite, organization) def user_invite(user_invite, organization)
@user_invite = user_invite @user_invite = user_invite
@organization = organization @organization = organization
mail :to => @user_invite.email_address, :subject => "Access the #{organization.name} organization on Postal" mail to: @user_invite.email_address, subject: "Access the #{organization.name} organization on Postal"
end end
def verify_domain(domain, email_address, user) def verify_domain(domain, email_address, user)
@domain = domain @domain = domain
@email_address = email_address @email_address = email_address
@user = user @user = user
mail :to => email_address, :subject => "Verify your ownership of #{@domain.name}" mail to: email_address, subject: "Verify your ownership of #{@domain.name}"
end end
def password_reset(user, return_to = nil) def password_reset(user, return_to = nil)
@user = user @user = user
@return_to = return_to @return_to = return_to
mail :to => @user.email_address, :subject => "Reset your Postal password" mail to: @user.email_address, subject: "Reset your Postal password"
end end
def server_send_limit_approaching(server) def server_send_limit_approaching(server)
@server = server @server = server
mail :to => @server.organization.notification_addresses, :subject => "[#{server.full_permalink}] Mail server is approaching its send limit" mail to: @server.organization.notification_addresses, subject: "[#{server.full_permalink}] Mail server is approaching its send limit"
end end
def server_send_limit_exceeded(server) def server_send_limit_exceeded(server)
@server = server @server = server
mail :to => @server.organization.notification_addresses, :subject => "[#{server.full_permalink}] Mail server has exceeded its send limit" mail to: @server.organization.notification_addresses, subject: "[#{server.full_permalink}] Mail server has exceeded its send limit"
end end
def server_suspended(server) def server_suspended(server)
@server = server @server = server
mail :to => @server.organization.notification_addresses, :subject => "[#{server.full_permalink}] Your mail server has been suspended" mail to: @server.organization.notification_addresses, subject: "[#{server.full_permalink}] Your mail server has been suspended"
end end
def test_message(recipient) def test_message(recipient)
mail :to => recipient, :subject => "Postal SMTP Test Message" mail to: recipient, subject: "Postal SMTP Test Message"
end end
end end
+3 -1
View File
@@ -1,4 +1,6 @@
class ApplicationMailer < ActionMailer::Base class ApplicationMailer < ActionMailer::Base
default :from => "#{Postal.smtp_from_name} <#{Postal.smtp_from_address}>"
default from: "#{Postal.smtp_from_name} <#{Postal.smtp_from_address}>"
layout false layout false
end end
+24 -27
View File
@@ -13,22 +13,21 @@
class AdditionalRouteEndpoint < ApplicationRecord class AdditionalRouteEndpoint < ApplicationRecord
belongs_to :route belongs_to :route
belongs_to :endpoint, :polymorphic => true belongs_to :endpoint, polymorphic: true
validate :validate_endpoint_belongs_to_server validate :validate_endpoint_belongs_to_server
validate :validate_wildcard validate :validate_wildcard
validate :validate_uniqueness validate :validate_uniqueness
def self.find_by_endpoint(endpoint) 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) unless Route::ENDPOINT_TYPES.include?(class_name)
raise Postal::Error, "Invalid endpoint class name '#{class_name}'" raise Postal::Error, "Invalid endpoint class name '#{class_name}'"
end end
if uuid = class_name.constantize.find_by_uuid(id)
where(:endpoint_type => class_name, :endpoint_id => uuid).first return unless uuid = class_name.constantize.find_by_uuid(id)
else
nil where(endpoint_type: class_name, endpoint_id: uuid).first
end
end end
def _endpoint def _endpoint
@@ -38,39 +37,37 @@ class AdditionalRouteEndpoint < ApplicationRecord
def _endpoint=(value) def _endpoint=(value)
if value.blank? if value.blank?
self.endpoint = nil self.endpoint = nil
else elsif value =~ /\#/
if value =~ /\#/ class_name, id = value.split("#", 2)
class_name, id = value.split('#', 2) unless Route::ENDPOINT_TYPES.include?(class_name)
unless Route::ENDPOINT_TYPES.include?(class_name) raise Postal::Error, "Invalid endpoint class name '#{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
self.endpoint = class_name.constantize.find_by_uuid(id)
else
self.endpoint = nil
end end
end end
private private
def validate_endpoint_belongs_to_server def validate_endpoint_belongs_to_server
if self.endpoint && self.endpoint&.server != self.route.server return unless endpoint && endpoint&.server != route.server
errors.add :endpoint, :invalid
end errors.add :endpoint, :invalid
end end
def validate_uniqueness def validate_uniqueness
if self.endpoint == self.route.endpoint return unless endpoint == route.endpoint
errors.add :base, "You can only add an endpoint to a route once"
end errors.add :base, "You can only add an endpoint to a route once"
end end
def validate_wildcard def validate_wildcard
if self.route.wildcard? return unless route.wildcard?
if self.endpoint_type == 'SMTPEndpoint' || self.endpoint_type == 'AddressEndpoint' return unless endpoint_type == "SMTPEndpoint" || endpoint_type == "AddressEndpoint"
errors.add :base, "SMTP or address endpoints are not permitted on wildcard routes"
end errors.add :base, "SMTP or address endpoints are not permitted on wildcard routes"
end
end end
end end
+6 -6
View File
@@ -16,10 +16,10 @@ class AddressEndpoint < ApplicationRecord
include HasUUID include HasUUID
belongs_to :server belongs_to :server
has_many :routes, :as => :endpoint has_many :routes, as: :endpoint
has_many :additional_route_endpoints, :dependent => :destroy, :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 before_destroy :update_routes
@@ -28,15 +28,15 @@ class AddressEndpoint < ApplicationRecord
end end
def update_routes def update_routes
self.routes.each { |r| r.update(:endpoint => nil, :mode => 'Reject') } routes.each { |r| r.update(endpoint: nil, mode: "Reject") }
end end
def description def description
self.address address
end end
def domain def domain
address.split('@', 2).last address.split("@", 2).last
end end
end end
+3 -1
View File
@@ -1,5 +1,7 @@
class ApplicationRecord < ActiveRecord::Base class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true self.abstract_class = true
self.inheritance_column = 'sti_type' self.inheritance_column = "sti_type"
nilify_blanks nilify_blanks
end end
+6 -3
View File
@@ -5,7 +5,7 @@ module HasMessage
end end
def message def message
@message ||= self.server.message_db.message(self.message_id) @message ||= server.message_db.message(message_id)
end end
def message=(message) def message=(message)
@@ -14,6 +14,7 @@ module HasMessage
end end
module ClassMethods module ClassMethods
def include_message def include_message
queued_messages = all.to_a queued_messages = all.to_a
server_ids = queued_messages.map(&:server_id).uniq server_ids = queued_messages.map(&:server_id).uniq
@@ -22,10 +23,11 @@ module HasMessage
elsif server_ids.size > 1 elsif server_ids.size > 1
raise Postal::Error, "'include_message' can only be used on collections of messages from the same server" raise Postal::Error, "'include_message' can only be used on collections of messages from the same server"
end end
message_ids = queued_messages.map(&:message_id).uniq message_ids = queued_messages.map(&:message_id).uniq
server = queued_messages.first&.server server = queued_messages.first&.server
messages = server.message_db.messages(:where => {:id => message_ids}).each_with_object({}) do |message, hash| messages = server.message_db.messages(where: { id: message_ids }).index_by do |message|
hash[message.id] = message message.id
end end
queued_messages.each do |queued_message| queued_messages.each do |queued_message|
if m = messages[queued_message.message_id] if m = messages[queued_message.message_id]
@@ -33,6 +35,7 @@ module HasMessage
end end
end end
end end
end end
end end
+4 -4
View File
@@ -3,16 +3,16 @@ module HasSoftDestroy
def self.included(base) def self.included(base)
base.define_callbacks :soft_destroy base.define_callbacks :soft_destroy
base.class_eval do base.class_eval do
scope :deleted, -> { where.not(:deleted_at => nil) } scope :deleted, -> { where.not(deleted_at: nil) }
scope :present, -> { where(:deleted_at => nil) } scope :present, -> { where(deleted_at: nil) }
end end
end end
def soft_destroy def soft_destroy
run_callbacks :soft_destroy do run_callbacks :soft_destroy do
self.deleted_at = Time.now self.deleted_at = Time.now
self.save! save!
ActionDeletionJob.queue(:main, :type => self.class.name, :id => self.id) ActionDeletionJob.queue(:main, type: self.class.name, id: id)
end end
end end
+3 -1
View File
@@ -1,11 +1,13 @@
module HasUUID module HasUUID
def self.included(base) def self.included(base)
base.class_eval do base.class_eval do
random_string :uuid, :type => :uuid, :unique => true random_string :uuid, type: :uuid, unique: true
end end
end end
def to_param def to_param
uuid uuid
end end
end end
+17 -18
View File
@@ -21,11 +21,11 @@ class Credential < ApplicationRecord
belongs_to :server belongs_to :server
TYPES = ['SMTP', 'API', 'SMTP-IP'] TYPES = ["SMTP", "API", "SMTP-IP"]
validates :key, :presence => true, :uniqueness => true validates :key, presence: true, uniqueness: true
validates :type, :inclusion => {:in => TYPES} validates :type, inclusion: { in: TYPES }
validates :name, :presence => true validates :name, presence: true
validate :validate_key_cannot_be_changed validate :validate_key_cannot_be_changed
validate :validate_key_for_smtp_ip validate :validate_key_for_smtp_ip
@@ -33,10 +33,9 @@ class Credential < ApplicationRecord
before_validation :generate_key before_validation :generate_key
def generate_key def generate_key
return if self.type == 'SMTP-IP' return if type == "SMTP-IP"
return if self.persisted? return if persisted?
self.key = SecureRandomString.new(24) self.key = SecureRandomString.new(24)
end end
@@ -51,26 +50,26 @@ class Credential < ApplicationRecord
def usage_type def usage_type
if last_used_at.nil? if last_used_at.nil?
'Unused' "Unused"
elsif last_used_at < 1.year.ago elsif last_used_at < 1.year.ago
'Inactive' "Inactive"
elsif last_used_at < 6.months.ago elsif last_used_at < 6.months.ago
'Dormant' "Dormant"
elsif last_used_at < 1.month.ago elsif last_used_at < 1.month.ago
'Quiet' "Quiet"
else else
'Active' "Active"
end end
end end
def to_smtp_plain def to_smtp_plain
Base64.encode64("\0XX\0#{self.key}").strip Base64.encode64("\0XX\0#{key}").strip
end end
def ipaddr def ipaddr
return unless type == 'SMTP-IP' return unless type == "SMTP-IP"
@ipaddr ||= IPAddr.new(self.key) @ipaddr ||= IPAddr.new(key)
rescue IPAddr::InvalidAddressError rescue IPAddr::InvalidAddressError
nil nil
end end
@@ -80,15 +79,15 @@ class Credential < ApplicationRecord
def validate_key_cannot_be_changed def validate_key_cannot_be_changed
return if new_record? return if new_record?
return unless key_changed? return unless key_changed?
return if type == 'SMTP-IP' return if type == "SMTP-IP"
errors.add :key, "cannot be changed" errors.add :key, "cannot be changed"
end end
def validate_key_for_smtp_ip 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 rescue IPAddr::InvalidAddressError
errors.add :key, "must be a valid IPv4 or IPv6 address" errors.add :key, "must be a valid IPv4 or IPv6 address"
end end
+33 -31
View File
@@ -34,39 +34,39 @@
# index_domains_on_uuid (uuid) # index_domains_on_uuid (uuid)
# #
require 'resolv' require "resolv"
class Domain < ApplicationRecord class Domain < ApplicationRecord
include HasUUID include HasUUID
require_dependency 'domain/dns_checks' require_dependency "domain/dns_checks"
require_dependency 'domain/dns_verification' 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 :server, optional: true
belongs_to :owner, :optional => true, :polymorphic => true belongs_to :owner, optional: true, polymorphic: true
has_many :routes, :dependent => :destroy has_many :routes, dependent: :destroy
has_many :track_domains, :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 :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 :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 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 before_save do
if self.verification_method == 'DNS' if verification_method == "DNS"
self.verification_token = Nifty::Utils::RandomString.generate(:length => 32) self.verification_token = Nifty::Utils::RandomString.generate(length: 32)
elsif self.verification_method == 'Email' elsif verification_method == "Email"
self.verification_token = rand(999999).to_s.ljust(6, '0') self.verification_token = rand(999_999).to_s.ljust(6, "0")
else else
self.verification_token = nil self.verification_token = nil
end end
@@ -79,13 +79,13 @@ class Domain < ApplicationRecord
def verify def verify
self.verified_at = Time.now self.verified_at = Time.now
self.save! save!
end end
def parent_domains def parent_domains
parts = self.name.split('.') parts = name.split(".")
parts[0,parts.size-1].each_with_index.map do |p, i| parts[0, parts.size - 1].each_with_index.map do |p, i|
parts[i..-1].join('.') parts[i..-1].join(".")
end end
end end
@@ -94,7 +94,7 @@ class Domain < ApplicationRecord
end end
def dkim_key def dkim_key
@dkim_key ||= OpenSSL::PKey::RSA.new(self.dkim_private_key) @dkim_key ||= OpenSSL::PKey::RSA.new(dkim_private_key)
end end
def to_param def to_param
@@ -114,12 +114,12 @@ class Domain < ApplicationRecord
end end
def dkim_record 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};" "v=DKIM1; t=s; h=sha256; p=#{public_key};"
end end
def dkim_identifier def dkim_identifier
Postal.config.dns.dkim_identifier + "-#{self.dkim_identifier_string}" Postal.config.dns.dkim_identifier + "-#{dkim_identifier_string}"
end end
def dkim_record_name def dkim_record_name
@@ -127,7 +127,7 @@ class Domain < ApplicationRecord
end end
def return_path_domain def return_path_domain
"#{Postal.config.dns.custom_return_path_prefix}.#{self.name}" "#{Postal.config.dns.custom_return_path_prefix}.#{name}"
end end
def nameservers def nameservers
@@ -135,7 +135,7 @@ class Domain < ApplicationRecord
end end
def resolver 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 end
private private
@@ -143,15 +143,17 @@ class Domain < ApplicationRecord
def get_nameservers def get_nameservers
local_resolver = Resolv::DNS.new local_resolver = Resolv::DNS.new
ns_records = [] ns_records = []
parts = name.split('.') parts = name.split(".")
(parts.size - 1).times do |n| (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 } 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 end
return [] if ns_records.blank? 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? return [] if ns_records.blank?
ns_records ns_records
end end
+46 -48
View File
@@ -1,9 +1,9 @@
require 'resolv' require "resolv"
class Domain class Domain
def dns_ok? 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 end
def dns_checked? def dns_checked?
@@ -16,21 +16,21 @@ class Domain
check_mx_records check_mx_records
check_return_path_record check_return_path_record
self.dns_checked_at = Time.now self.dns_checked_at = Time.now
self.save! save!
if source == :auto && !dns_ok? && self.owner.is_a?(Server) if source == :auto && !dns_ok? && owner.is_a?(Server)
WebhookRequest.trigger(self.owner, 'DomainDNSError', { WebhookRequest.trigger(owner, "DomainDNSError", {
:server => self.owner.webhook_hash, server: owner.webhook_hash,
:domain => self.name, domain: name,
:uuid => self.uuid, uuid: uuid,
:dns_checked_at => self.dns_checked_at.to_f, dns_checked_at: dns_checked_at.to_f,
:spf_status => self.spf_status, spf_status: spf_status,
:spf_error => self.spf_error, spf_error: spf_error,
:dkim_status => self.dkim_status, dkim_status: dkim_status,
:dkim_error => self.dkim_error, dkim_error: dkim_error,
:mx_status => self.mx_status, mx_status: mx_status,
:mx_error => self.mx_error, mx_error: mx_error,
:return_path_status => self.return_path_status, return_path_status: return_path_status,
:return_path_error => self.return_path_error return_path_error: return_path_error
}) })
end end
dns_ok? dns_ok?
@@ -41,19 +41,19 @@ class Domain
# #
def check_spf_record def check_spf_record
result = resolver.getresources(self.name, Resolv::DNS::Resource::IN::TXT) result = resolver.getresources(name, Resolv::DNS::Resource::IN::TXT)
spf_records = result.map(&:data).select { |d| d =~ /\Av=spf1/} spf_records = result.map(&:data).select { |d| d =~ /\Av=spf1/ }
if spf_records.empty? if spf_records.empty?
self.spf_status = 'Missing' self.spf_status = "Missing"
self.spf_error = 'No SPF record exists for this domain' self.spf_error = "No SPF record exists for this domain"
else 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? 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}" self.spf_error = "An SPF record exists but it doesn't include #{Postal.config.dns.spf_include}"
false false
else else
self.spf_status = 'OK' self.spf_status = "OK"
self.spf_error = nil self.spf_error = nil
true true
end end
@@ -68,24 +68,24 @@ class Domain
# #
# DKIM # DKIM
# #
def check_dkim_record def check_dkim_record
domain = "#{dkim_record_name}.#{name}" domain = "#{dkim_record_name}.#{name}"
result = resolver.getresources(domain, Resolv::DNS::Resource::IN::TXT) result = resolver.getresources(domain, Resolv::DNS::Resource::IN::TXT)
records = result.map(&:data) records = result.map(&:data)
if records.empty? if records.empty?
self.dkim_status = 'Missing' self.dkim_status = "Missing"
self.dkim_error = "No TXT records were returned for #{domain}" self.dkim_error = "No TXT records were returned for #{domain}"
else 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 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." self.dkim_error = "There are #{records.size} records for at #{domain}. There should only be one."
elsif sanitised_dkim_record != self.dkim_record elsif sanitised_dkim_record != dkim_record
self.dkim_status = 'Invalid' 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." self.dkim_error = "The DKIM record at #{domain} does not match the record we have provided. Please check it has been copied correctly."
else else
self.dkim_status = 'OK' self.dkim_status = "OK"
self.dkim_error = nil self.dkim_error = nil
true true
end end
@@ -102,21 +102,21 @@ class Domain
# #
def check_mx_records 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) records = result.map(&:exchange)
if records.empty? if records.empty?
self.mx_status = 'Missing' self.mx_status = "Missing"
self.mx_error = "There are no MX records for #{self.name}" self.mx_error = "There are no MX records for #{name}"
else else
missing_records = Postal.config.dns.mx_records.dup - records.map { |r| r.to_s.downcase } missing_records = Postal.config.dns.mx_records.dup - records.map { |r| r.to_s.downcase }
if missing_records.empty? if missing_records.empty?
self.mx_status = 'OK' self.mx_status = "OK"
self.mx_error = nil self.mx_error = nil
elsif missing_records.size == Postal.config.dns.mx_records.size elsif missing_records.size == Postal.config.dns.mx_records.size
self.mx_status = 'Missing' self.mx_status = "Missing"
self.mx_error = 'You have MX records but none of them point to us.' self.mx_error = "You have MX records but none of them point to us."
else 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." self.mx_error = "MX #{missing_records.size == 1 ? 'record' : 'records'} for #{missing_records.to_sentence} are missing and are required."
end end
end end
@@ -132,19 +132,17 @@ class Domain
# #
def check_return_path_record 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 } records = result.map { |r| r.name.to_s.downcase }
if records.empty? if records.empty?
self.return_path_status = 'Missing' self.return_path_status = "Missing"
self.return_path_error = "There is no return path record at #{self.return_path_domain}" 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 else
if records.size == 1 && records.first == Postal.config.dns.return_path self.return_path_status = "Invalid"
self.return_path_status = 'OK' 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}."
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
end end
end end
+6 -5
View File
@@ -1,4 +1,4 @@
require 'resolv' require "resolv"
class Domain class Domain
@@ -7,11 +7,12 @@ class Domain
end end
def verify_with_dns def verify_with_dns
return false unless self.verification_method == 'DNS' return false unless 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) 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.verified_at = Time.now
self.save save
else else
false false
end end
+10 -10
View File
@@ -26,19 +26,19 @@ class HTTPEndpoint < ApplicationRecord
include HasUUID include HasUUID
belongs_to :server belongs_to :server
has_many :routes, :as => :endpoint has_many :routes, as: :endpoint
has_many :additional_route_endpoints, :dependent => :destroy, :as => :endpoint has_many :additional_route_endpoints, dependent: :destroy, as: :endpoint
ENCODINGS = ['BodyAsJSON', 'FormData'] ENCODINGS = ["BodyAsJSON", "FormData"]
FORMATS = ['Hash', 'RawMessage'] FORMATS = ["Hash", "RawMessage"]
before_destroy :update_routes before_destroy :update_routes
validates :name, :presence => true validates :name, presence: true
validates :url, :presence => true validates :url, presence: true
validates :encoding, :inclusion => {:in => ENCODINGS} validates :encoding, inclusion: { in: ENCODINGS }
validates :format, :inclusion => {:in => FORMATS} validates :format, inclusion: { in: FORMATS }
validates :timeout, :numericality => {:greater_than_or_equal_to => 5, :less_than_or_equal_to => 60} validates :timeout, numericality: { greater_than_or_equal_to: 5, less_than_or_equal_to: 60 }
default_value :timeout, -> { DEFAULT_TIMEOUT } default_value :timeout, -> { DEFAULT_TIMEOUT }
@@ -51,7 +51,7 @@ class HTTPEndpoint < ApplicationRecord
end end
def update_routes def update_routes
self.routes.each { |r| r.update(:endpoint => nil, :mode => 'Reject') } routes.each { |r| r.update(endpoint: nil, mode: "Reject") }
end end
end end
+16 -20
View File
@@ -18,27 +18,23 @@ class IncomingMessagePrototype
end end
def from_address def from_address
@from.gsub(/.*</, '').gsub(/>.*/, '').strip @from.gsub(/.*</, "").gsub(/>.*/, "").strip
end end
def route def route
@routes ||= begin @routes ||= if @to.present?
if @to.present? uname, domain = @to.split("@", 2)
uname, domain = @to.split('@', 2) uname, tag = uname.split("+", 2)
uname, tag = uname.split('+', 2) @server.routes.includes(:domain).where(domains: { name: domain }, name: uname).first
@server.routes.includes(:domain).where(:domains => {:name => domain}, :name => uname).first end
else
nil
end
end
end end
def attachments def attachments
(@attachments || []).map do |attachment| (@attachments || []).map do |attachment|
{ {
:name => attachment[:name], name: attachment[:name],
:content_type => attachment[:content_type] || 'application/octet-stream', content_type: attachment[:content_type] || "application/octet-stream",
:data => attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data] data: attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data]
} }
end end
end end
@@ -47,10 +43,10 @@ class IncomingMessagePrototype
if valid? if valid?
messages = route.create_messages do |message| messages = route.create_messages do |message|
message.rcpt_to = @to message.rcpt_to = @to
message.mail_from = self.from_address message.mail_from = from_address
message.raw_message = self.raw_message message.raw_message = raw_message
end end
{route.description => {:id => messages.first.id, :token => messages.first.token}} { route.description => { id: messages.first.id, token: messages.first.token } }
else else
false false
end end
@@ -66,7 +62,7 @@ class IncomingMessagePrototype
end end
def validate def validate
@errors = Array.new @errors = []
if route.nil? if route.nil?
@errors << "NoRoutesFound" @errors << "NoRoutesFound"
end end
@@ -91,11 +87,11 @@ class IncomingMessagePrototype
mail.message_id = "<#{SecureRandom.uuid}@#{Postal.config.dns.return_path}>" mail.message_id = "<#{SecureRandom.uuid}@#{Postal.config.dns.return_path}>"
attachments.each do |attachment| attachments.each do |attachment|
mail.attachments[attachment[:name]] = { mail.attachments[attachment[:name]] = {
:mime_type => attachment[:content_type], mime_type: attachment[:content_type],
:content => attachment[:data] content: attachment[:data]
} }
end 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 mail.to_s
end end
end end
+3 -3
View File
@@ -16,9 +16,9 @@ class IPAddress < ApplicationRecord
belongs_to :ip_pool belongs_to :ip_pool
validates :ipv4, :presence => true, :uniqueness => true validates :ipv4, presence: true, uniqueness: true
validates :hostname, :presence => true validates :hostname, presence: true
validates :ipv6, :uniqueness => {:allow_blank => 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 } 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) } scope :order_by_priority, -> { order(priority: :desc) }
+7 -7
View File
@@ -18,16 +18,16 @@ class IPPool < ApplicationRecord
include HasUUID include HasUUID
validates :name, :presence => true validates :name, presence: true
has_many :ip_addresses, :dependent => :restrict_with_exception has_many :ip_addresses, dependent: :restrict_with_exception
has_many :servers, :dependent => :restrict_with_exception has_many :servers, dependent: :restrict_with_exception
has_many :organization_ip_pools, :dependent => :destroy has_many :organization_ip_pools, dependent: :destroy
has_many :organizations, :through => :organization_ip_pools has_many :organizations, through: :organization_ip_pools
has_many :ip_pool_rules, :dependent => :destroy has_many :ip_pool_rules, dependent: :destroy
def self.default def self.default
where(:default => true).order(:id).first where(default: true).order(:id).first
end end
end end
+17 -16
View File
@@ -17,24 +17,24 @@ class IPPoolRule < ApplicationRecord
include HasUUID include HasUUID
belongs_to :owner, :polymorphic => true belongs_to :owner, polymorphic: true
belongs_to :ip_pool belongs_to :ip_pool
validate :validate_from_and_to_addresses validate :validate_from_and_to_addresses
validate :validate_ip_pool_belongs_to_organization validate :validate_ip_pool_belongs_to_organization
def from def from
from_text ? from_text.gsub(/\r/, '').split(/\n/).map(&:strip) : [] from_text ? from_text.gsub(/\r/, "").split(/\n/).map(&:strip) : []
end end
def to def to
to_text ? to_text.gsub(/\r/, '').split(/\n/).map(&:strip) : [] to_text ? to_text.gsub(/\r/, "").split(/\n/).map(&:strip) : []
end end
def apply_to_message?(message) def apply_to_message?(message)
if from.present? && message.headers['from'].present? if from.present? && message.headers["from"].present?
from.each do |condition| 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 return true
end end
end end
@@ -54,28 +54,29 @@ class IPPoolRule < ApplicationRecord
private private
def validate_from_and_to_addresses def validate_from_and_to_addresses
if self.from.empty? && self.to.empty? return unless from.empty? && to.empty?
errors.add :base, "At least one rule condition must be specified"
end errors.add :base, "At least one rule condition must be specified"
end end
def validate_ip_pool_belongs_to_organization def validate_ip_pool_belongs_to_organization
org = self.owner.is_a?(Organization) ? self.owner : self.owner.organization org = owner.is_a?(Organization) ? owner : owner.organization
if self.ip_pool && self.ip_pool_id_changed? && !org.ip_pools.include?(self.ip_pool) 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 errors.add :ip_pool_id, "must belong to the organization"
end end
def self.address_matches?(condition, address) def self.address_matches?(condition, address)
address = Postal::Helpers.strip_name_from_address(address) address = Postal::Helpers.strip_name_from_address(address)
if condition =~ /@/ if condition =~ /@/
parts = address.split('@') parts = address.split("@")
domain, uname = parts.pop, parts.join('@') domain = parts.pop
uname, _ = uname.split('+', 2) uname = parts.join("@")
uname, = uname.split("+", 2)
condition == "#{uname}@#{domain}" condition == "#{uname}@#{domain}"
else else
# Match as a domain # Match as a domain
condition == address.split('@').last condition == address.split("@").last
end end
end end
+28 -28
View File
@@ -23,41 +23,41 @@
class Organization < ApplicationRecord 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_QUOTA = 10
INITIAL_SUPER_QUOTA = 10000 INITIAL_SUPER_QUOTA = 10_000
include HasUUID include HasUUID
include HasSoftDestroy include HasSoftDestroy
validates :name, :presence => true validates :name, presence: true
validates :permalink, :presence => true, :format => {:with => /\A[a-z0-9\-]*\z/}, :uniqueness => true, :exclusion => {:in => RESERVED_PERMALINKS} validates :permalink, presence: true, format: { with: /\A[a-z0-9-]*\z/ }, uniqueness: true, exclusion: { in: RESERVED_PERMALINKS }
validates :time_zone, :presence => true validates :time_zone, presence: true
default_value :time_zone, -> { 'UTC' } default_value :time_zone, -> { "UTC" }
default_value :permalink, -> { Organization.find_unique_permalink(self.name) if self.name } default_value :permalink, -> { Organization.find_unique_permalink(name) if name }
belongs_to :owner, :class_name => 'User' belongs_to :owner, class_name: "User"
has_many :organization_users, :dependent => :destroy has_many :organization_users, dependent: :destroy
has_many :users, :through => :organization_users, :source_type => 'User' has_many :users, through: :organization_users, source_type: "User"
has_many :user_invites, :through => :organization_users, :source_type => 'UserInvite', :source => :user has_many :user_invites, through: :organization_users, source_type: "UserInvite", source: :user
has_many :servers, :dependent => :destroy has_many :servers, dependent: :destroy
has_many :domains, :as => :owner, :dependent => :destroy has_many :domains, as: :owner, dependent: :destroy
has_many :organization_ip_pools, :dependent => :destroy has_many :organization_ip_pools, dependent: :destroy
has_many :ip_pools, :through => :organization_ip_pools has_many :ip_pools, through: :organization_ip_pools
has_many :ip_pool_rules, :dependent => :destroy, :as => :owner has_many :ip_pool_rules, dependent: :destroy, as: :owner
after_create do after_create do
if pool = IPPool.default if pool = IPPool.default
self.ip_pools << IPPool.default ip_pools << IPPool.default
end end
end end
def status def status
if self.suspended? if suspended?
'Suspended' "Suspended"
else else
'Active' "Active"
end end
end end
@@ -71,25 +71,25 @@ class Organization < ApplicationRecord
def user_assignment(user) def user_assignment(user)
@user_assignments ||= {} @user_assignments ||= {}
@user_assignments[user.id] ||= organization_users.where(:user => user).first @user_assignments[user.id] ||= organization_users.where(user: user).first
end end
def make_owner(new_owner) def make_owner(new_owner)
user_assignment(new_owner).update(:admin => true, :all_servers => true) user_assignment(new_owner).update(admin: true, all_servers: true)
update(:owner => new_owner) update(owner: new_owner)
end end
# This is an array of addresses that should receive notifications for this organization # This is an array of addresses that should receive notifications for this organization
def notification_addresses def notification_addresses
self.users.map(&:email_tag) users.map(&:email_tag)
end end
def self.find_unique_permalink(name) def self.find_unique_permalink(name)
loop.each_with_index do |_, i| loop.each_with_index do |_, i|
i = i + 1 i += 1
proposal = name.parameterize proposal = name.parameterize
proposal += "-#{i}" if i > 1 proposal += "-#{i}" if i > 1
unless self.where(:permalink => proposal).exists? unless where(permalink: proposal).exists?
return proposal return proposal
end end
end end
@@ -97,9 +97,9 @@ class Organization < ApplicationRecord
def self.[](id) def self.[](id)
if id.is_a?(String) if id.is_a?(String)
where(:permalink => id).first where(permalink: id).first
else else
where(:id => id.to_i).first where(id: id.to_i).first
end end
end end
+2
View File
@@ -10,6 +10,8 @@
# #
class OrganizationIPPool < ApplicationRecord class OrganizationIPPool < ApplicationRecord
belongs_to :organization belongs_to :organization
belongs_to :ip_pool belongs_to :ip_pool
end end
+1 -1
View File
@@ -14,6 +14,6 @@
class OrganizationUser < ApplicationRecord class OrganizationUser < ApplicationRecord
belongs_to :organization belongs_to :organization
belongs_to :user, :polymorphic => true, :optional => true belongs_to :user, polymorphic: true, optional: true
end end
+33 -31
View File
@@ -1,4 +1,4 @@
require 'resolv' require "resolv"
class OutgoingMessagePrototype class OutgoingMessagePrototype
@@ -29,9 +29,7 @@ class OutgoingMessagePrototype
end end
end end
def message_id attr_reader :message_id
@message_id
end
def from_address def from_address
Postal::Helpers.strip_name_from_address(@from) Postal::Helpers.strip_name_from_address(@from)
@@ -59,15 +57,15 @@ class OutgoingMessagePrototype
end end
def to_addresses 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 end
def cc_addresses 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 end
def bcc_addresses 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 end
def all_addresses def all_addresses
@@ -98,30 +96,30 @@ class OutgoingMessagePrototype
def attachments def attachments
(@attachments || []).map do |attachment| (@attachments || []).map do |attachment|
{ {
:name => attachment[:name], name: attachment[:name],
:content_type => attachment[:content_type] || 'application/octet-stream', content_type: attachment[:content_type] || "application/octet-stream",
:data => attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data] data: attachment[:base64] ? Base64.decode64(attachment[:data]) : attachment[:data]
} }
end end
end end
def validate def validate
@errors = Array.new @errors = []
if to_addresses.empty? && cc_addresses.empty? && bcc_addresses.empty? if to_addresses.empty? && cc_addresses.empty? && bcc_addresses.empty?
@errors << "NoRecipients" @errors << "NoRecipients"
end end
if to_addresses.size > 50 if to_addresses.size > 50
@errors << 'TooManyToAddresses' @errors << "TooManyToAddresses"
end end
if cc_addresses.size > 50 if cc_addresses.size > 50
@errors << 'TooManyCCAddresses' @errors << "TooManyCCAddresses"
end end
if bcc_addresses.size > 50 if bcc_addresses.size > 50
@errors << 'TooManyBCCAddresses' @errors << "TooManyBCCAddresses"
end end
if @plain_body.blank? && @html_body.blank? if @plain_body.blank? && @html_body.blank?
@@ -136,7 +134,7 @@ class OutgoingMessagePrototype
@errors << "UnauthenticatedFromAddress" @errors << "UnauthenticatedFromAddress"
end end
if attachments && !attachments.empty? if attachments.present?
attachments.each_with_index do |attachment, index| attachments.each_with_index do |attachment, index|
if attachment[:name].blank? if attachment[:name].blank?
@errors << "AttachmentMissingName" unless @errors.include?("AttachmentMissingName") @errors << "AttachmentMissingName" unless @errors.include?("AttachmentMissingName")
@@ -154,18 +152,18 @@ class OutgoingMessagePrototype
if @custom_headers.is_a?(Hash) if @custom_headers.is_a?(Hash)
@custom_headers.each { |key, value| mail[key.to_s] = value.to_s } @custom_headers.each { |key, value| mail[key.to_s] = value.to_s }
end end
mail.to = self.to_addresses.join(', ') if self.to_addresses.present? mail.to = to_addresses.join(", ") if to_addresses.present?
mail.cc = self.cc_addresses.join(', ') if self.cc_addresses.present? mail.cc = cc_addresses.join(", ") if cc_addresses.present?
mail.from = @from mail.from = @from
mail.sender = @sender mail.sender = @sender
mail.subject = @subject mail.subject = @subject
mail.reply_to = @reply_to mail.reply_to = @reply_to
mail.part :content_type => "multipart/alternative" do |p| mail.part content_type: "multipart/alternative" do |p|
if !@plain_body.blank? if @plain_body.present?
p.text_part = Mail::Part.new p.text_part = Mail::Part.new
p.text_part.body = @plain_body p.text_part.body = @plain_body
end end
if !@html_body.blank? if @html_body.present?
p.html_part = Mail::Part.new p.html_part = Mail::Part.new
p.html_part.content_type = "text/html; charset=UTF-8" p.html_part.content_type = "text/html; charset=UTF-8"
p.html_part.body = @html_body p.html_part.body = @html_body
@@ -173,11 +171,11 @@ class OutgoingMessagePrototype
end end
attachments.each do |attachment| attachments.each do |attachment|
mail.attachments[attachment[:name]] = { mail.attachments[attachment[:name]] = {
:mime_type => attachment[:content_type], mime_type: attachment[:content_type],
:content => attachment[:data] content: attachment[:data]
} }
end 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.message_id = "<#{@message_id}>"
mail.to_s mail.to_s
end end
@@ -185,21 +183,25 @@ class OutgoingMessagePrototype
def create_message(address) def create_message(address)
message = @server.message_db.new_message message = @server.message_db.new_message
message.scope = 'outgoing' message.scope = "outgoing"
message.rcpt_to = address message.rcpt_to = address
message.mail_from = self.from_address message.mail_from = from_address
message.domain_id = self.domain.id message.domain_id = domain.id
message.raw_message = self.raw_message message.raw_message = raw_message
message.tag = self.tag message.tag = tag
message.credential_id = self.credential&.id message.credential_id = credential&.id
message.received_with_ssl = true message.received_with_ssl = true
message.bounce = @bounce ? 1 : 0 message.bounce = @bounce ? 1 : 0
message.save message.save
{:id => message.id, :token => message.token} { id: message.id, token: message.token }
end end
def resolved_hostname def resolved_hostname
@resolved_hostname ||= Resolv.new.getname(@ip) rescue @ip @resolved_hostname ||= begin
Resolv.new.getname(@ip)
rescue StandardError
@ip
end
end end
end end
+21 -20
View File
@@ -29,21 +29,21 @@ class QueuedMessage < ApplicationRecord
include HasMessage include HasMessage
belongs_to :server belongs_to :server
belongs_to :ip_address, :optional => true belongs_to :ip_address, optional: true
belongs_to :user, :optional => true belongs_to :user, optional: true
before_create :allocate_ip_address 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) } scope :retriable, -> { where("retry_after IS NULL OR retry_after <= ?", 30.seconds.from_now) }
def retriable? def retriable?
self.retry_after.nil? || self.retry_after <= 30.seconds.from_now retry_after.nil? || retry_after <= 30.seconds.from_now
end end
def queue def queue
UnqueueMessageJob.queue(queue_name, :id => self.id) UnqueueMessageJob.queue(queue_name, id: id)
end end
def queue! def queue!
@@ -56,21 +56,21 @@ class QueuedMessage < ApplicationRecord
end end
def send_bounce def send_bounce
if self.message.send_bounces? return unless message.send_bounces?
Postal::BounceMessage.new(self.server, self.message).queue
end Postal::BounceMessage.new(server, message).queue
end end
def allocate_ip_address def allocate_ip_address
if Postal.ip_pools? && self.message && pool = self.server.ip_pool_for_message(self.message) return unless Postal.ip_pools? && message && pool = server.ip_pool_for_message(message)
self.ip_address = pool.ip_addresses.select_by_priority
end self.ip_address = pool.ip_addresses.select_by_priority
end end
def acquire_lock def acquire_lock
time = Time.now time = Time.now
locker = Postal.locker_name 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 if rows == 1
self.locked_by = locker self.locked_by = locker
self.locked_at = time self.locked_at = time
@@ -81,20 +81,20 @@ class QueuedMessage < ApplicationRecord
end end
def retry_later(time = nil) 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_by = nil
self.locked_at = 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 end
def unlock def unlock
self.locked_by = nil self.locked_by = nil
self.locked_at = nil self.locked_at = nil
update_columns(:locked_by => nil, :locked_at => nil) update_columns(locked_by: nil, locked_at: nil)
end end
def self.calculate_retry_time(attempts, initial_period) def self.calculate_retry_time(attempts, initial_period)
(1.3 ** attempts) * initial_period (1.3**attempts) * initial_period
end end
def locked? def locked?
@@ -105,13 +105,14 @@ class QueuedMessage < ApplicationRecord
unless locked? unless locked?
raise Postal::Error, "Must lock current message before locking any friends" raise Postal::Error, "Must lock current message before locking any friends"
end end
if self.batch_key.nil?
if batch_key.nil?
[] []
else else
time = Time.now time = Time.now
locker = Postal.locker_name 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) 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 => self.batch_key, :ip_address_id => self.ip_address_id, :locked_by => locker, :locked_at => time).where.not(id: self.id) QueuedMessage.where(batch_key: batch_key, ip_address_id: ip_address_id, locked_by: locker, locked_at: time).where.not(id: id)
end end
end end
+79 -83
View File
@@ -22,22 +22,22 @@
class Route < ApplicationRecord class Route < ApplicationRecord
MODES = ['Endpoint', 'Accept', 'Hold', 'Bounce', 'Reject'] MODES = ["Endpoint", "Accept", "Hold", "Bounce", "Reject"]
include HasUUID include HasUUID
belongs_to :server belongs_to :server
belongs_to :domain, :optional => true belongs_to :domain, optional: true
belongs_to :endpoint, :polymorphic => true, :optional => true belongs_to :endpoint, polymorphic: true, optional: true
has_many :additional_route_endpoints, :dependent => :destroy has_many :additional_route_endpoints, dependent: :destroy
SPAM_MODES = ['Mark', 'Quarantine', 'Fail'] SPAM_MODES = ["Mark", "Quarantine", "Fail"]
ENDPOINT_TYPES = ['SMTPEndpoint', 'HTTPEndpoint', 'AddressEndpoint'] ENDPOINT_TYPES = ["SMTPEndpoint", "HTTPEndpoint", "AddressEndpoint"]
validates :name, :presence => true, :format => /\A(([a-z0-9\-\.]*)|(\*)|(__returnpath__))\z/ validates :name, presence: true, format: /\A(([a-z0-9\-.]*)|(\*)|(__returnpath__))\z/
validates :spam_mode, :inclusion => {:in => SPAM_MODES} validates :spam_mode, inclusion: { in: SPAM_MODES }
validates :endpoint, :presence => {:if => proc { self.mode == 'Endpoint' }} validates :endpoint, presence: { if: proc { mode == "Endpoint" } }
validates :domain_id, :presence => {:unless => :return_path?} validates :domain_id, presence: { unless: :return_path? }
validate :validate_route_is_routed validate :validate_route_is_routed
validate :validate_domain_belongs_to_server validate :validate_domain_belongs_to_server
validate :validate_endpoint_belongs_to_server validate :validate_endpoint_belongs_to_server
@@ -47,7 +47,7 @@ class Route < ApplicationRecord
after_save :save_additional_route_endpoints 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? def return_path?
name == "__returnpath__" name == "__returnpath__"
@@ -62,12 +62,10 @@ class Route < ApplicationRecord
end end
def _endpoint def _endpoint
@endpoint ||= begin if mode == "Endpoint"
if self.mode == 'Endpoint' @endpoint ||= endpoint ? "#{endpoint.class}##{endpoint.uuid}" : nil
endpoint ? "#{endpoint.class}##{endpoint.uuid}" : nil else
else @endpoint ||= mode
self.mode
end
end end
end end
@@ -75,18 +73,17 @@ class Route < ApplicationRecord
if value.blank? if value.blank?
self.endpoint = nil self.endpoint = nil
self.mode = nil self.mode = nil
else elsif value =~ /\#/
if value =~ /\#/ class_name, id = value.split("#", 2)
class_name, id = value.split('#', 2) unless ENDPOINT_TYPES.include?(class_name)
unless ENDPOINT_TYPES.include?(class_name) raise Postal::Error, "Invalid endpoint class name '#{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
self.endpoint = class_name.constantize.find_by_uuid(id)
self.mode = "Endpoint"
else
self.endpoint = nil
self.mode = value
end end
end end
@@ -95,7 +92,7 @@ class Route < ApplicationRecord
end end
def wildcard? def wildcard?
self.name == '*' name == "*"
end end
def additional_route_endpoints_array def additional_route_endpoints_array
@@ -107,50 +104,51 @@ class Route < ApplicationRecord
end end
def save_additional_route_endpoints def save_additional_route_endpoints
if @additional_route_endpoints_array return unless @additional_route_endpoints_array
seen = []
@additional_route_endpoints_array.each do |item| seen = []
if existing = additional_route_endpoints.find_by_endpoint(item) @additional_route_endpoints_array.each do |item|
seen << existing.id 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 else
route = additional_route_endpoints.build(:_endpoint => item) route.errors.each do |field, message|
if route.save errors.add :base, message
seen << route.id
else
route.errors.each do |field, message|
errors.add :base, message
end
raise ActiveRecord::RecordInvalid
end end
raise ActiveRecord::RecordInvalid
end end
end end
additional_route_endpoints.where.not(:id => seen).destroy_all
end end
additional_route_endpoints.where.not(id: seen).destroy_all
end end
# #
# This message will create a suitable number of message objects for messages that # 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. # but most information is specified already.
# #
# Returns an array of created messages. # Returns an array of created messages.
# #
def create_messages(&block) def create_messages(&block)
messages = [] messages = []
message = self.build_message message = build_message
if self.mode == 'Endpoint' && self.server.message_db.schema_version >= 18 if mode == "Endpoint" && server.message_db.schema_version >= 18
message.endpoint_type = self.endpoint_type message.endpoint_type = endpoint_type
message.endpoint_id = self.endpoint_id message.endpoint_id = endpoint_id
end end
block.call(message) block.call(message)
message.save message.save
messages << message messages << message
# Also create any messages for additional endpoints that might exist # Also create any messages for additional endpoints that might exist
if self.mode == 'Endpoint' && self.server.message_db.schema_version >= 18 if mode == "Endpoint" && server.message_db.schema_version >= 18
self.additional_route_endpoints.each do |endpoint| additional_route_endpoints.each do |endpoint|
next unless endpoint.endpoint next unless endpoint.endpoint
message = self.build_message
message = build_message
message.endpoint_id = endpoint.endpoint_id message.endpoint_id = endpoint.endpoint_id
message.endpoint_type = endpoint.endpoint_type message.endpoint_type = endpoint.endpoint_type
block.call(message) block.call(message)
@@ -163,69 +161,67 @@ class Route < ApplicationRecord
end end
def build_message def build_message
message = self.server.message_db.new_message message = server.message_db.new_message
message.scope = 'incoming' message.scope = "incoming"
message.rcpt_to = self.description message.rcpt_to = description
message.domain_id = self.domain&.id message.domain_id = domain&.id
message.route_id = self.id message.route_id = id
message message
end end
private private
def validate_route_is_routed def validate_route_is_routed
if self.mode.nil? return unless mode.nil?
errors.add :endpoint, "must be chosen"
end errors.add :endpoint, "must be chosen"
end end
def validate_domain_belongs_to_server 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 errors.add :domain, :invalid
end end
if self.domain && !self.domain.verified? return unless domain && !domain.verified?
errors.add :domain, "has not been verified yet"
end errors.add :domain, "has not been verified yet"
end end
def validate_endpoint_belongs_to_server def validate_endpoint_belongs_to_server
if self.endpoint && self.endpoint&.server != self.server return unless endpoint && endpoint&.server != server
errors.add :endpoint, :invalid
end errors.add :endpoint, :invalid
end end
def validate_name_uniqueness def validate_name_uniqueness
return if self.server.nil? return if 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 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" errors.add :name, "is configured on the #{route.server.full_permalink} mail server"
end end
else elsif route = Route.where(name: "__returnpath__").where.not(id: id).exists?
if route = Route.where(:name => "__returnpath__").where.not(:id => self.id).exists? errors.add :base, "A return path route already exists for this server"
errors.add :base, "A return path route already exists for this server"
end
end end
end end
def validate_return_path_route_endpoints def validate_return_path_route_endpoints
if return_path? return unless return_path?
if self.mode != 'Endpoint' || self.endpoint_type != 'HTTPEndpoint' return unless mode != "Endpoint" || endpoint_type != "HTTPEndpoint"
errors.add :base, "Return path routes must point to an HTTP endpoint"
end errors.add :base, "Return path routes must point to an HTTP endpoint"
end
end end
def validate_no_additional_routes_on_non_endpoint_route def validate_no_additional_routes_on_non_endpoint_route
if self.mode != 'Endpoint' && !self.additional_route_endpoints_array.empty? 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 errors.add :base, "Additional routes are not permitted unless the primary route is an actual endpoint"
end end
def self.find_by_name_and_domain(name, domain) 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? if route.nil?
route = Route.includes(:domain).where(:name => '*', :domains => {:name => domain}).first route = Route.includes(:domain).where(name: "*", domains: { name: domain }).first
end end
route route
end end
+93 -93
View File
@@ -41,7 +41,7 @@
class Server < ApplicationRecord 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 HasUUID
include HasSoftDestroy include HasSoftDestroy
@@ -49,55 +49,55 @@ class Server < ApplicationRecord
attr_accessor :provision_database attr_accessor :provision_database
belongs_to :organization belongs_to :organization
belongs_to :ip_pool, :optional => true belongs_to :ip_pool, optional: true
has_many :domains, :dependent => :destroy, :as => :owner has_many :domains, dependent: :destroy, as: :owner
has_many :credentials, :dependent => :destroy has_many :credentials, dependent: :destroy
has_many :smtp_endpoints, :dependent => :destroy has_many :smtp_endpoints, dependent: :destroy
has_many :http_endpoints, :dependent => :destroy has_many :http_endpoints, dependent: :destroy
has_many :address_endpoints, :dependent => :destroy has_many :address_endpoints, dependent: :destroy
has_many :routes, :dependent => :destroy has_many :routes, dependent: :destroy
has_many :queued_messages, :dependent => :delete_all has_many :queued_messages, dependent: :delete_all
has_many :webhooks, :dependent => :destroy has_many :webhooks, dependent: :destroy
has_many :webhook_requests, :dependent => :destroy has_many :webhook_requests, dependent: :destroy
has_many :track_domains, :dependent => :destroy has_many :track_domains, dependent: :destroy
has_many :ip_pool_rules, :dependent => :destroy, :as => :owner 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 random_string :token, type: :chars, length: 6, unique: true, upper_letters_only: true
default_value :permalink, -> { name ? name.parameterize : nil} default_value :permalink, -> { name ? name.parameterize : nil }
default_value :raw_message_retention_days, -> { 30 } default_value :raw_message_retention_days, -> { 30 }
default_value :raw_message_retention_size, -> { 2048 } default_value :raw_message_retention_size, -> { 2048 }
default_value :message_retention_days, -> { 60 } default_value :message_retention_days, -> { 60 }
default_value :spam_threshold, -> { Postal.config.general.default_spam_threshold } default_value :spam_threshold, -> { Postal.config.general.default_spam_threshold }
default_value :spam_failure_threshold, -> { Postal.config.general.default_spam_failure_threshold } default_value :spam_failure_threshold, -> { Postal.config.general.default_spam_failure_threshold }
validates :name, :presence => true, :uniqueness => {:scope => :organization_id} validates :name, presence: true, uniqueness: { scope: :organization_id }
validates :mode, :inclusion => {:in => MODES} 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 :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 validate :validate_ip_pool_belongs_to_organization
before_validation(:on => :create) do before_validation(on: :create) do
self.token = self.token.downcase if self.token self.token = token.downcase if token
end end
after_create do after_create do
unless self.provision_database == false unless provision_database == false
message_db.provisioner.provision message_db.provisioner.provision
end end
end end
after_commit(:on => :destroy) do after_commit(on: :destroy) do
unless self.provision_database == false unless provision_database == false
message_db.provisioner.drop message_db.provisioner.drop
end end
end end
def status def status
if self.suspended? if suspended?
'Suspended' "Suspended"
else else
self.mode mode
end end
end end
@@ -110,12 +110,12 @@ class Server < ApplicationRecord
end end
def actual_suspension_reason def actual_suspension_reason
if suspended? return unless suspended?
if suspended_at.nil?
organization.suspension_reason if suspended_at.nil?
else organization.suspension_reason
self.suspension_reason else
end suspension_reason
end end
end end
@@ -124,30 +124,28 @@ class Server < ApplicationRecord
end end
def message_db def message_db
@message_db ||= Postal::MessageDB::Database.new(self.organization_id, self.id) @message_db ||= Postal::MessageDB::Database.new(organization_id, id)
end end
def message(id) delegate :message, to: :message_db
message_db.message(id)
end
def message_rate 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 end
def held_messages def held_messages
@held_messages ||= message_db.messages(:where => {:held => 1}, :count => true) @held_messages ||= message_db.messages(where: { held: 1 }, count: true)
end end
def throughput_stats def throughput_stats
@throughput_stats ||= begin @throughput_stats ||= begin
incoming = message_db.live_stats.total(60, :types => [:incoming]) incoming = message_db.live_stats.total(60, types: [:incoming])
outgoing = message_db.live_stats.total(60, :types => [:outgoing]) outgoing = message_db.live_stats.total(60, types: [:outgoing])
outgoing_usage = send_limit ? (outgoing / send_limit.to_f) * 100 : 0 outgoing_usage = send_limit ? (outgoing / send_limit.to_f) * 100 : 0
{ {
:incoming => incoming, incoming: incoming,
:outgoing => outgoing, outgoing: outgoing,
:outgoing_usage => outgoing_usage outgoing_usage: outgoing_usage
} }
end end
end end
@@ -166,8 +164,10 @@ class Server < ApplicationRecord
end end
def domain_stats def domain_stats
domains = Domain.where(:owner_id => self.id, :owner_type => 'Server').to_a domains = Domain.where(owner_id: id, owner_type: "Server").to_a
total, unverified, bad_dns = 0, 0, 0 total = 0
unverified = 0
bad_dns = 0
domains.each do |domain| domains.each do |domain|
total += 1 total += 1
unverified += 1 unless domain.verified? unverified += 1 unless domain.verified?
@@ -178,29 +178,29 @@ class Server < ApplicationRecord
def webhook_hash def webhook_hash
{ {
:uuid => self.uuid, uuid: uuid,
:name => self.name, name: name,
:permalink => self.permalink, permalink: permalink,
:organization => self.organization&.permalink organization: organization&.permalink
} }
end end
def send_volume def send_volume
@send_volume ||= message_db.live_stats.total(60, :types => [:outgoing]) @send_volume ||= message_db.live_stats.total(60, types: [:outgoing])
end end
def send_limit_approaching? def send_limit_approaching?
self.send_limit && (send_volume >= self.send_limit * 0.90) send_limit && (send_volume >= send_limit * 0.90)
end end
def send_limit_exceeded? def send_limit_exceeded?
self.send_limit && send_volume >= self.send_limit send_limit && send_volume >= send_limit
end end
def send_limit_warning(type) def send_limit_warning(type)
AppMailer.send("server_send_limit_#{type}", self).deliver AppMailer.send("server_send_limit_#{type}", self).deliver
self.update_column("send_limit_#{type}_notified_at", Time.now) 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) WebhookRequest.trigger(self, "SendLimit#{type.to_s.capitalize}", server: webhook_hash, volume: send_volume, limit: send_limit)
end end
def queue_size def queue_size
@@ -209,36 +209,38 @@ class Server < ApplicationRecord
def stats def stats
{ {
:queue => queue_size, queue: queue_size,
:held => self.held_messages, held: held_messages,
:bounce_rate => self.bounce_rate, bounce_rate: bounce_rate,
:message_rate => self.message_rate, message_rate: message_rate,
:throughput => self.throughput_stats, throughput: throughput_stats,
:size => self.message_db.total_size size: message_db.total_size
} }
end end
def authenticated_domain_for_address(address) def authenticated_domain_for_address(address)
return nil if address.blank? return nil if address.blank?
address = Postal::Helpers.strip_name_from_address(address) 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 uname
return nil unless domain_name return nil unless domain_name
uname, _ = uname.split('+', 2)
uname, = uname.split("+", 2)
# Check the server's domain # 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 return domain
end end
if any_domain = self.domains.verified.where(:use_for_any => true).order(:name).first return unless any_domain = domains.verified.where(use_for_any: true).order(:name).first
return any_domain
end any_domain
end end
def find_authenticated_domain_from_headers(headers) def find_authenticated_domain_from_headers(headers)
header_to_check = ['from'] header_to_check = ["from"]
header_to_check << 'sender' if self.allow_sender? header_to_check << "sender" if allow_sender?
header_to_check.each do |header_name| header_to_check.each do |header_name|
if headers[header_name].is_a?(Array) if headers[header_name].is_a?(Array)
values = headers[header_name] values = headers[header_name]
@@ -257,36 +259,34 @@ class Server < ApplicationRecord
def suspend(reason) def suspend(reason)
self.suspended_at = Time.now self.suspended_at = Time.now
self.suspension_reason = reason self.suspension_reason = reason
self.save! save!
AppMailer.server_suspended(self).deliver AppMailer.server_suspended(self).deliver
end end
def unsuspend def unsuspend
self.suspended_at = nil self.suspended_at = nil
self.suspension_reason = nil self.suspension_reason = nil
self.save! save!
end end
def validate_ip_pool_belongs_to_organization def validate_ip_pool_belongs_to_organization
if self.ip_pool && self.ip_pool_id_changed? && !self.organization.ip_pools.include?(self.ip_pool) 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 errors.add :ip_pool_id, "must belong to the organization"
end end
def ip_pool_for_message(message) def ip_pool_for_message(message)
if message.scope == 'outgoing' return unless message.scope == "outgoing"
[self, self.organization].each do |scope|
rules = scope.ip_pool_rules.order(:created_at => :desc) [self, organization].each do |scope|
rules.each do |rule| rules = scope.ip_pool_rules.order(created_at: :desc)
if rule.apply_to_message?(message) rules.each do |rule|
return rule.ip_pool if rule.apply_to_message?(message)
end return rule.ip_pool
end end
end end
self.ip_pool
else
nil
end end
ip_pool
end end
def self.triggered_send_limit(type) def self.triggered_send_limit(type)
@@ -297,12 +297,12 @@ class Server < ApplicationRecord
def self.send_send_limit_notifications def self.send_send_limit_notifications
[:approaching, :exceeded].each_with_object({}) do |type, hash| [:approaching, :exceeded].each_with_object({}) do |type, hash|
hash[type] = 0 hash[type] = 0
servers = self.triggered_send_limit(type) servers = triggered_send_limit(type)
unless servers.empty? next if servers.empty?
servers.each do |server|
hash[type] += 1 servers.each do |server|
server.send_limit_warning(type) hash[type] += 1
end server.send_limit_warning(type)
end end
end end
end end
@@ -311,15 +311,15 @@ class Server < ApplicationRecord
server = nil server = nil
if id.is_a?(String) if id.is_a?(String)
if id =~ /\A(\w+)\/(\w+)\z/ 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 end
else else
server = where(:id => id).first server = where(id: id).first
end end
if extra if extra
if extra.is_a?(String) if extra.is_a?(String)
server.domains.where(:name => extra.to_s).first server.domains.where(name: extra.to_s).first
else else
server.message(extra.to_i) server.message(extra.to_i)
end end
+8 -8
View File
@@ -21,17 +21,17 @@ class SMTPEndpoint < ApplicationRecord
include HasUUID include HasUUID
belongs_to :server belongs_to :server
has_many :routes, :as => :endpoint has_many :routes, as: :endpoint
has_many :additional_route_endpoints, :dependent => :destroy, :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 before_destroy :update_routes
validates :name, :presence => true validates :name, presence: true
validates :hostname, :presence => true, :format => /\A[a-z0-9\.\-]*\z/ validates :hostname, presence: true, format: /\A[a-z0-9.-]*\z/
validates :ssl_mode, :inclusion => {:in => SSL_MODES} validates :ssl_mode, inclusion: { in: SSL_MODES }
validates :port, :numericality => {:only_integer => true, :allow_blank => true} validates :port, numericality: { only_integer: true, allow_blank: true }
def description def description
"#{name} (#{hostname})" "#{name} (#{hostname})"
@@ -42,7 +42,7 @@ class SMTPEndpoint < ApplicationRecord
end end
def update_routes def update_routes
self.routes.each { |r| r.update(:endpoint => nil, :mode => 'Reject') } routes.each { |r| r.update(endpoint: nil, mode: "Reject") }
end end
end end
+19 -21
View File
@@ -18,7 +18,7 @@
# excluded_click_domains :text(65535) # excluded_click_domains :text(65535)
# #
require 'resolv' require "resolv"
class TrackDomain < ApplicationRecord class TrackDomain < ApplicationRecord
@@ -27,16 +27,16 @@ class TrackDomain < ApplicationRecord
belongs_to :server belongs_to :server
belongs_to :domain belongs_to :domain
validates :name, :presence => true, :format => {:with => /\A[a-z0-9\-]+\z/}, :uniqueness => {:scope => :domain_id, :message => "is already added"} 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 :domain_id, uniqueness: { scope: :server_id, message: "already has a track domain for this server" }
validate :validate_domain_belongs_to_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 before_validation do
self.server = self.domain.server if self.domain && self.server.nil? self.server = domain.server if domain && server.nil?
end end
def full_name def full_name
@@ -48,26 +48,24 @@ class TrackDomain < ApplicationRecord
end end
def dns_ok? def dns_ok?
self.dns_status == 'OK' dns_status == "OK"
end end
def check_dns 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 } records = result.map { |r| r.name.to_s.downcase }
if records.empty? if records.empty?
self.dns_status = 'Missing' self.dns_status = "Missing"
self.dns_error = "There is no record at #{self.full_name}" 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 else
if records.size == 1 && records.first == Postal.config.dns.track_domain self.dns_status = "Invalid"
self.dns_status = 'OK' 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}."
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
end end
self.dns_checked_at = Time.now self.dns_checked_at = Time.now
self.save! save!
dns_ok? dns_ok?
end end
@@ -76,9 +74,9 @@ class TrackDomain < ApplicationRecord
end end
def validate_domain_belongs_to_server def validate_domain_belongs_to_server
if self.domain && ![self.server, self.server.organization].include?(self.domain.owner) return unless domain && ![server, server.organization].include?(domain.owner)
errors.add :domain, "does not belong to the server or the server's organization"
end errors.add :domain, "does not belong to the server or the server's organization"
end end
end end
+13 -15
View File
@@ -27,25 +27,23 @@ class User < ApplicationRecord
include HasUUID include HasUUID
require_dependency 'user/authentication' require_dependency "user/authentication"
validates :first_name, :presence => true validates :first_name, presence: true
validates :last_name, :presence => true validates :last_name, presence: true
validates :email_address, :presence => true, :uniqueness => true, :format => {:with => /@/, allow_blank: true} validates :email_address, presence: true, uniqueness: true, format: { with: /@/, allow_blank: true }
validates :time_zone, :presence => 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 :organization_users, dependent: :destroy, as: :user
has_many :organizations, :through => :organization_users has_many :organizations, through: :organization_users
def organizations_scope def organizations_scope
@organizations_scope ||= begin if admin?
if self.admin? @organizations_scope ||= Organization.present
Organization.present else
else @organizations_scope ||= organizations.present
self.organizations.present
end
end end
end end
@@ -70,7 +68,7 @@ class User < ApplicationRecord
end end
def self.[](email) def self.[](email)
where(:email_address => email).first where(email_address: email).first
end end
end end
+8 -7
View File
@@ -2,9 +2,9 @@ class User
has_secure_password 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 before_save do
self.password_reset_token = nil self.password_reset_token = nil
self.password_reset_token_valid_until = nil self.password_reset_token_valid_until = nil
@@ -12,9 +12,10 @@ class User
end end
def self.authenticate(email_address, password) def self.authenticate(email_address, password)
user = where(:email_address => email_address).first user = where(email_address: email_address).first
raise Postal::Errors::AuthenticationError.new('InvalidEmailAddress') if user.nil? raise Postal::Errors::AuthenticationError, "InvalidEmailAddress" if user.nil?
raise Postal::Errors::AuthenticationError.new('InvalidPassword') unless user.authenticate(password) raise Postal::Errors::AuthenticationError, "InvalidPassword" unless user.authenticate(password)
user user
end end
@@ -27,9 +28,9 @@ class User
end end
def begin_password_reset(return_to = nil) 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.password_reset_token_valid_until = 1.day.from_now
self.save! save!
AppMailer.password_reset(self, return_to).deliver AppMailer.password_reset(self, return_to).deliver
end end
+8 -8
View File
@@ -18,10 +18,10 @@ class UserInvite < ApplicationRecord
include HasUUID 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 :organization_users, dependent: :destroy, as: :user
has_many :organizations, :through => :organization_users has_many :organizations, through: :organization_users
default_value :expires_at, -> { 7.days.from_now } default_value :expires_at, -> { 7.days.from_now }
@@ -41,16 +41,16 @@ class UserInvite < ApplicationRecord
def accept(user) def accept(user)
transaction do transaction do
self.organization_users.each do |ou| organization_users.each do |ou|
ou.update(:user => user) || ou.destroy ou.update(user: user) || ou.destroy
end end
self.organization_users.reload organization_users.reload
self.destroy destroy
end end
end end
def reject def reject
self.destroy destroy
end end
end end
+11 -11
View File
@@ -24,19 +24,19 @@ class Webhook < ApplicationRecord
include HasUUID include HasUUID
belongs_to :server belongs_to :server
has_many :webhook_events, :dependent => :destroy has_many :webhook_events, dependent: :destroy
has_many :webhook_requests has_many :webhook_requests
validates :name, :presence => true validates :name, presence: true
validates :url, :presence => true, :format => {:with => /\Ahttps?\:\/\/[a-z0-9\-\.\_\?\=\&\/\+:%@]+\z/i, :allow_blank => 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 after_save :save_events
when_attribute :all_events, :changes_to => true do when_attribute :all_events, changes_to: true do
after_save do after_save do
self.webhook_events.destroy_all webhook_events.destroy_all
end end
end end
@@ -49,12 +49,12 @@ class Webhook < ApplicationRecord
end end
def save_events def save_events
if @events return unless @events
@events.each do |event|
webhook_events.where(:event => event).first_or_create! @events.each do |event|
end webhook_events.where(event: event).first_or_create!
webhook_events.where.not(:event => @events).destroy_all
end end
webhook_events.where.not(event: @events).destroy_all
end end
end end
+9 -9
View File
@@ -15,18 +15,18 @@
class WebhookEvent < ApplicationRecord class WebhookEvent < ApplicationRecord
EVENTS = [ EVENTS = [
'MessageSent', "MessageSent",
'MessageDelayed', "MessageDelayed",
'MessageDeliveryFailed', "MessageDeliveryFailed",
'MessageHeld', "MessageHeld",
'MessageBounced', "MessageBounced",
'MessageLinkClicked', "MessageLinkClicked",
'MessageLoaded', "MessageLoaded",
'DomainDNSError' "DomainDNSError"
] ]
belongs_to :webhook belongs_to :webhook
validates :event, :presence => true validates :event, presence: true
end end
+28 -28
View File
@@ -19,17 +19,17 @@ class WebhookRequest < ApplicationRecord
include HasUUID 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 :server
belongs_to :webhook, :optional => true belongs_to :webhook, optional: true
validates :url, :presence => true validates :url, presence: true
validates :event, :presence => true validates :event, presence: true
serialize :payload, Hash serialize :payload, Hash
after_commit :queue, :on => :create after_commit :queue, on: :create
def self.trigger(server, event, payload = {}) def self.trigger(server, event, payload = {})
unless server.is_a?(Server) 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 = server.webhooks.enabled.includes(:webhook_events).references(:webhook_events).where("webhooks.all_events = ? OR webhook_events.event = ?", true, event)
webhooks.each do |webhook| 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
end end
def self.requeue_all def self.requeue_all
where("retry_after < ?", Time.now).each(&:queue) where("retry_after < ?", Time.now).find_each(&:queue)
end end
def queue def queue
WebhookDeliveryJob.queue(:main, :id => self.id) WebhookDeliveryJob.queue(:main, id: id)
end end
def deliver def deliver
logger = Postal.logger_for(:webhooks) logger = Postal.logger_for(:webhooks)
payload = {:event => self.event, :timestamp => self.created_at.to_f, :payload => self.payload, :uuid => self.uuid}.to_json payload = { event: event, timestamp: created_at.to_f, payload: self.payload, uuid: uuid }.to_json
logger.info "[#{id}] Sending webhook request to `#{self.url}`" logger.info "[#{id}] Sending webhook request to `#{url}`"
result = Postal::HTTP.post(self.url, :sign => true, :json => payload, :timeout => 5) result = Postal::HTTP.post(url, sign: true, json: payload, timeout: 5)
self.attempts += 1 self.attempts += 1
self.retry_after = RETRIES[self.attempts]&.from_now self.retry_after = RETRIES[self.attempts]&.from_now
self.server.message_db.webhooks.record( server.message_db.webhooks.record(
:event => self.event, event: event,
:url => self.url, url: url,
:webhook_id => self.webhook_id, webhook_id: webhook_id,
:attempt => self.attempts, attempt: self.attempts,
:timestamp => Time.now.to_f, timestamp: Time.now.to_f,
:payload => self.payload.to_json, payload: self.payload.to_json,
:uuid => self.uuid, uuid: uuid,
:status_code => result[:code], status_code: result[:code],
:body => result[:body], body: result[:body],
:will_retry => (self.retry_after ? 0 : 1) will_retry: (retry_after ? 0 : 1)
) )
if result[:code] >= 200 && result[:code] < 300 if result[:code] >= 200 && result[:code] < 300
logger.info "[#{id}] -> Received #{result[:code]} status code. That's OK." logger.info "[#{id}] -> Received #{result[:code]} status code. That's OK."
self.destroy destroy
self.webhook&.update_column(:last_used_at, Time.now) webhook&.update_column(:last_used_at, Time.now)
true true
else else
logger.error "[#{id}] -> Received #{result[:code]} status code. That's not OK." logger.error "[#{id}] -> Received #{result[:code]} status code. That's not OK."
self.error = "Couldn't send to URL. Code received was #{result[:code]}" self.error = "Couldn't send to URL. Code received was #{result[:code]}"
if self.retry_after if retry_after
logger.info "[#{id}] -> Will retry #{self.retry_after} (this was attempt #{self.attempts})" logger.info "[#{id}] -> Will retry #{retry_after} (this was attempt #{self.attempts})"
self.save save
else else
logger.info "[#{id}] -> Have tried #{self.attempts} times. Giving up." logger.info "[#{id}] -> Have tried #{self.attempts} times. Giving up."
self.destroy destroy
end end
false false
end end
+2 -2
View File
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
load Gem.bin_path('bundler', 'bundle') load Gem.bin_path("bundler", "bundle")
+3 -3
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
APP_PATH = File.expand_path('../config/application', __dir__) APP_PATH = File.expand_path("../config/application", __dir__)
require_relative '../config/boot' require_relative "../config/boot"
require 'rails/commands' require "rails/commands"
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require_relative '../config/boot' require_relative "../config/boot"
require 'rake' require "rake"
Rake.application.run Rake.application.run
+9 -9
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require 'pathname' require "pathname"
require 'fileutils' require "fileutils"
include FileUtils include FileUtils
# path to your application root. # path to your application root.
APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) APP_ROOT = Pathname.new File.expand_path("..", __dir__)
def system!(*args) def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==") system(*args) || abort("\n== Command #{args} failed ==")
@@ -14,9 +14,9 @@ chdir APP_ROOT do
# This script is a starting point to setup your application. # This script is a starting point to setup your application.
# Add necessary setup steps to this file. # Add necessary setup steps to this file.
puts '== Installing dependencies ==' puts "== Installing dependencies =="
system! 'gem install bundler --conservative' system! "gem install bundler --conservative"
system('bundle check') || system!('bundle install') system("bundle check") || system!("bundle install")
# puts "\n== Copying sample files ==" # puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml') # unless File.exist?('config/database.yml')
@@ -24,11 +24,11 @@ chdir APP_ROOT do
# end # end
puts "\n== Preparing database ==" puts "\n== Preparing database =="
system! 'bin/rails db:setup' system! "bin/rails db:setup"
puts "\n== Removing old logs and tempfiles ==" puts "\n== Removing old logs and tempfiles =="
system! 'bin/rails log:clear tmp:clear' system! "bin/rails log:clear tmp:clear"
puts "\n== Restarting application server ==" puts "\n== Restarting application server =="
system! 'bin/rails restart' system! "bin/rails restart"
end end
+9 -9
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require 'pathname' require "pathname"
require 'fileutils' require "fileutils"
include FileUtils include FileUtils
# path to your application root. # path to your application root.
APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) APP_ROOT = Pathname.new File.expand_path("..", __dir__)
def system!(*args) def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==") system(*args) || abort("\n== Command #{args} failed ==")
@@ -14,16 +14,16 @@ chdir APP_ROOT do
# This script is a way to update your development environment automatically. # This script is a way to update your development environment automatically.
# Add necessary update steps to this file. # Add necessary update steps to this file.
puts '== Installing dependencies ==' puts "== Installing dependencies =="
system! 'gem install bundler --conservative' system! "gem install bundler --conservative"
system('bundle check') || system!('bundle install') system("bundle check") || system!("bundle install")
puts "\n== Updating database ==" puts "\n== Updating database =="
system! 'bin/rails db:migrate' system! "bin/rails db:migrate"
puts "\n== Removing old logs and tempfiles ==" puts "\n== Removing old logs and tempfiles =="
system! 'bin/rails log:clear tmp:clear' system! "bin/rails log:clear tmp:clear"
puts "\n== Restarting application server ==" puts "\n== Restarting application server =="
system! 'bin/rails restart' system! "bin/rails restart"
end end
+2 -2
View File
@@ -1,5 +1,5 @@
# This file is used by Rack-based servers to start the application. # This file is used by Rack-based servers to start the application.
require_relative 'config/environment' require_relative "config/environment"
$0="[postal] #{ENV['PROC_NAME']}" $0 = "[postal] #{ENV.fetch('PROC_NAME', nil)}"
run Rails.application run Rails.application
+5 -3
View File
@@ -1,4 +1,4 @@
require_relative 'boot' require_relative "boot"
require "rails" require "rails"
require "active_model/railtie" require "active_model/railtie"
@@ -14,6 +14,7 @@ Bundler.require(*Rails.groups)
module Postal module Postal
class Application < Rails::Application class Application < Rails::Application
# Disable most generators # Disable most generators
config.generators do |g| config.generators do |g|
g.orm :active_record g.orm :active_record
@@ -27,12 +28,13 @@ module Postal
config.eager_load_namespaces << Postal config.eager_load_namespaces << Postal
# Disable field_with_errors # Disable field_with_errors
config.action_view.field_error_proc = Proc.new { |t, i| t } config.action_view.field_error_proc = proc { |t, i| t }
# Load the tracking server middleware # Load the tracking server middleware
require 'postal/tracking_middleware' require "postal/tracking_middleware"
config.middleware.use Postal::TrackingMiddleware config.middleware.use Postal::TrackingMiddleware
config.logger = Postal.logger_for(:rails) config.logger = Postal.logger_for(:rails)
end end
end end
+4 -4
View File
@@ -1,11 +1,11 @@
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
$stdout.sync = true $stdout.sync = true
$stderr.sync = true $stderr.sync = true
require 'bundler/setup' # Set up gems listed in the Gemfile. require "bundler/setup" # Set up gems listed in the Gemfile.
require_relative '../lib/postal/config' require_relative "../lib/postal/config"
Postal.check_config! Postal.check_config!
ENV['RAILS_ENV'] = Postal.config.rails&.environment || 'development' ENV["RAILS_ENV"] = Postal.config.rails&.environment || "development"
+5 -5
View File
@@ -1,26 +1,26 @@
module Clockwork module Clockwork
configure do |config| configure do |config|
config[:tz] = 'UTC' config[:tz] = "UTC"
config[:logger] = Postal.logger_for(:cron) config[:logger] = Postal.logger_for(:cron)
end end
every 1.minute, 'every-1-minutes' do every 1.minute, "every-1-minutes" do
RequeueWebhooksJob.queue(:main) RequeueWebhooksJob.queue(:main)
SendNotificationsJob.queue(:main) SendNotificationsJob.queue(:main)
end end
every 1.hour, 'every-hour', :at => ['**:15'] do every 1.hour, "every-hour", at: ["**:15"] do
CheckAllDNSJob.queue(:main) CheckAllDNSJob.queue(:main)
ExpireHeldMessagesJob.queue(:main) ExpireHeldMessagesJob.queue(:main)
CleanupAuthieSessionsJob.queue(:main) CleanupAuthieSessionsJob.queue(:main)
end end
every 1.hour, 'every-hour', :at => ['**:45'] do every 1.hour, "every-hour", at: ["**:45"] do
PruneWebhookRequestsJob.queue(:main) PruneWebhookRequestsJob.queue(:main)
end end
every 1.day, 'every-day', :at => ['03:00'] do every 1.day, "every-day", at: ["03:00"] do
ProcessMessageRetentionJob.queue(:main) ProcessMessageRetentionJob.queue(:main)
PruneSuppressionListsJob.queue(:main) PruneSuppressionListsJob.queue(:main)
end end
+1 -1
View File
@@ -1,5 +1,5 @@
# Load the Rails application. # Load the Rails application.
require_relative 'application' require_relative "application"
# Initialize the Rails application. # Initialize the Rails application.
Rails.application.initialize! Rails.application.initialize!
+2 -2
View File
@@ -13,12 +13,12 @@ Rails.application.configure do
config.consider_all_requests_local = true config.consider_all_requests_local = true
# Enable/disable caching. By default caching is disabled. # Enable/disable caching. By default caching is disabled.
if Rails.root.join('tmp/caching-dev.txt').exist? if Rails.root.join("tmp/caching-dev.txt").exist?
config.action_controller.perform_caching = true config.action_controller.perform_caching = true
config.cache_store = :memory_store config.cache_store = :memory_store
config.public_file_server.headers = { config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=172800' "Cache-Control" => "public, max-age=172800"
} }
else else
config.action_controller.perform_caching = false config.action_controller.perform_caching = false
+2 -3
View File
@@ -34,7 +34,6 @@ Rails.application.configure do
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true # config.force_ssl = true
@@ -43,7 +42,7 @@ Rails.application.configure do
config.log_level = :info config.log_level = :info
# Prepend all log lines with the following tags. # Prepend all log lines with the following tags.
config.log_tags = [ :request_id ] config.log_tags = [:request_id]
# Use a different cache store in production. # Use a different cache store in production.
# config.cache_store = :mem_cache_store # config.cache_store = :mem_cache_store
@@ -65,7 +64,7 @@ Rails.application.configure do
config.active_support.deprecation = :notify config.active_support.deprecation = :notify
# Use default logging formatter so that PID and timestamp are not suppressed. # Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new config.log_formatter = Logger::Formatter.new
# Use a different logger for distributed setups. # Use a different logger for distributed setups.
# require 'syslog/logger' # require 'syslog/logger'
+1 -1
View File
@@ -15,7 +15,7 @@ Rails.application.configure do
# Configure public file server for tests with Cache-Control for performance. # Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true config.public_file_server.enabled = true
config.public_file_server.headers = { config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=3600' "Cache-Control" => "public, max-age=3600"
} }
# Show full error reports and disable caching. # Show full error reports and disable caching.
+1 -1
View File
@@ -1,7 +1,7 @@
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets. # Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0' Rails.application.config.assets.version = "1.0"
# Add additional assets to the asset load path # Add additional assets to the asset load path
# Rails.application.config.assets.paths << Emoji.images_path # Rails.application.config.assets.paths << Emoji.images_path
+9 -9
View File
@@ -12,13 +12,13 @@
# These inflection rules are supported but not enabled by default: # These inflection rules are supported but not enabled by default:
ActiveSupport::Inflector.inflections(:en) do |inflect| ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'API' inflect.acronym "API"
inflect.acronym 'SMTP' inflect.acronym "SMTP"
inflect.acronym 'IP' inflect.acronym "IP"
inflect.acronym 'DNS' inflect.acronym "DNS"
inflect.acronym 'UUID' inflect.acronym "UUID"
inflect.acronym 'HTTP' inflect.acronym "HTTP"
inflect.acronym 'DB' inflect.acronym "DB"
inflect.acronym 'MX' inflect.acronym "MX"
inflect.acronym 'DKIM' inflect.acronym "DKIM"
end end
+65 -35
View File
@@ -1,37 +1,38 @@
require 'mail' require "mail"
module Mail module Mail
module Encodings module Encodings
# Handle windows-1258 as windows-1252 when decoding # Handle windows-1258 as windows-1252 when decoding
def Encodings.q_value_decode(str) def self.q_value_decode(str)
str = str.sub(/\=\?windows-?1258\?/i, '\=?windows-1252?') str = str.sub(/=\?windows-?1258\?/i, '\=?windows-1252?')
RubyVer.q_value_decode(str) RubyVer.q_value_decode(str)
end end
def Encodings.b_value_decode(str)
str = str.sub(/\=\?windows-?1258\?/i, '\=?windows-1252?') def self.b_value_decode(str)
str = str.sub(/=\?windows-?1258\?/i, '\=?windows-1252?')
RubyVer.b_value_decode(str) RubyVer.b_value_decode(str)
end end
end end
class Message class Message
## Extract plain text body of message ## Extract plain text body of message
def plain_body def plain_body
if self.multipart? and self.text_part if multipart? and text_part
self.text_part.decoded text_part.decoded
elsif self.mime_type == 'text/plain' || self.mime_type.nil? elsif mime_type == "text/plain" || mime_type.nil?
self.decoded decoded
else
nil
end end
end end
## Extract HTML text body of message ## Extract HTML text body of message
def html_body def html_body
if self.multipart? and self.html_part if multipart? and html_part
self.html_part.decoded html_part.decoded
elsif self.mime_type == 'text/html' elsif mime_type == "text/html"
self.decoded decoded
else
nil
end end
end end
@@ -46,9 +47,21 @@ module Mail
# Returns the filename of the attachment (if it exists) or returns nil # Returns the filename of the attachment (if it exists) or returns nil
# Make up a filename for rfc822 attachments if it isn't specified # Make up a filename for rfc822 attachments if it isn't specified
def find_attachment def find_attachment
content_type_name = header[:content_type].filename rescue nil content_type_name = begin
content_disp_name = header[:content_disposition].filename rescue nil header[:content_type].filename
content_loc_name = header[:content_location].location rescue nil rescue StandardError
nil
end
content_disp_name = begin
header[:content_disposition].filename
rescue StandardError
nil
end
content_loc_name = begin
header[:content_location].location
rescue StandardError
nil
end
if content_type && content_type_name if content_type && content_type_name
filename = content_type_name filename = content_type_name
@@ -56,56 +69,73 @@ module Mail
filename = content_disp_name filename = content_disp_name
elsif content_location && content_loc_name elsif content_location && content_loc_name
filename = content_loc_name filename = content_loc_name
elsif self.mime_type == "message/rfc822" elsif mime_type == "message/rfc822"
filename = "#{rand(100000000)}.eml" filename = "#{rand(100_000_000)}.eml"
else else
filename = nil filename = nil
end end
if filename if filename
# Normal decode # Normal decode
filename = Mail::Encodings.decode_encode(filename, :decode) rescue filename filename = begin
Mail::Encodings.decode_encode(filename, :decode)
rescue StandardError
filename
end
end end
filename filename
end end
def decode_body_as_text def decode_body_as_text
body_text = decode_body body_text = decode_body
charset_tmp = Encoding.find(Ruby19.pick_encoding(charset)) rescue 'ASCII' charset_tmp = begin
charset_tmp = 'Windows-1252' if charset_tmp.to_s =~ /windows-?1258/i Encoding.find(Ruby19.pick_encoding(charset))
if charset_tmp == Encoding.find('UTF-7') rescue StandardError
body_text.force_encoding('UTF-8') "ASCII"
decoded = body_text.gsub(/\+.*?\-/m) {|n|Base64.decode64(n[1..-2]+'===').force_encoding('UTF-16BE').encode('UTF-8')} end
charset_tmp = "Windows-1252" if charset_tmp.to_s =~ /windows-?1258/i
if charset_tmp == Encoding.find("UTF-7")
body_text.force_encoding("UTF-8")
decoded = body_text.gsub(/\+.*?-/m) { |n| Base64.decode64(n[1..-2] + "===").force_encoding("UTF-16BE").encode("UTF-8") }
else else
body_text.force_encoding(charset_tmp) body_text.force_encoding(charset_tmp)
decoded = body_text.encode("utf-8", :invalid => :replace, :undef => :replace) decoded = body_text.encode("utf-8", invalid: :replace, undef: :replace)
end end
decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :undef => :replace).encode("utf-8") decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", invalid: :replace, undef: :replace).encode("utf-8")
end end
end end
# Handle attached emails as attachments # Handle attached emails as attachments
class AttachmentsList < Array class AttachmentsList < Array
def initialize(parts_list) def initialize(parts_list)
@parts_list = parts_list @parts_list = parts_list
@content_disposition_type = 'attachment' @content_disposition_type = "attachment"
parts_list.map { |p| parts_list.map do |p|
(p.parts.empty? and p.attachment?) ? p : p.attachments (p.parts.empty? and p.attachment?) ? p : p.attachments
}.flatten.compact.each { |a| self << a } end.flatten.compact.each { |a| self << a }
self self
end end
end end
end end
class Array class Array
def decoded def decoded
return nil if self.empty? return nil if empty?
return self.first.decoded
first.decoded
end end
end end
class NilClass class NilClass
def decoded def decoded
nil nil
end end
end end
+2 -2
View File
@@ -1,2 +1,2 @@
require 'postal' require "postal"
require 'postal/message_db/mysql' require "postal/message_db/mysql"
@@ -1,5 +1,6 @@
module ActionView module ActionView
module RecordIdentifier module RecordIdentifier
def dom_id(record, prefix = nil) def dom_id(record, prefix = nil)
if record.new_record? if record.new_record?
dom_class(record, prefix || NEW) dom_class(record, prefix || NEW)
@@ -8,5 +9,6 @@ module ActionView
"#{dom_class(record, prefix)}#{JOIN}#{id}" "#{dom_class(record, prefix)}#{JOIN}#{id}"
end end
end end
end end
end end
+1 -1
View File
@@ -1,6 +1,6 @@
if Postal.config.rails&.secret_key if Postal.config.rails&.secret_key
Rails.application.secrets.secret_key_base = Postal.config.rails.secret_key Rails.application.secrets.secret_key_base = Postal.config.rails.secret_key
else else
$stderr.puts "No secret key was specified in the Postal config file. Using one for just this session" warn "No secret key was specified in the Postal config file. Using one for just this session"
Rails.application.secrets.secret_key_base = SecureRandom.hex(128) Rails.application.secrets.secret_key_base = SecureRandom.hex(128)
end end
-2
View File
@@ -1,10 +1,8 @@
SecureHeaders::Configuration.default do |config| SecureHeaders::Configuration.default do |config|
config.hsts = SecureHeaders::OPT_OUT config.hsts = SecureHeaders::OPT_OUT
config.csp[:default_src] = [] config.csp[:default_src] = []
config.csp[:script_src] = ["'self'"] config.csp[:script_src] = ["'self'"]
config.csp[:child_src] = ["'self'"] config.csp[:child_src] = ["'self'"]
config.csp[:connect_src] = ["'self'"] config.csp[:connect_src] = ["'self'"]
end end
+6 -6
View File
@@ -1,14 +1,14 @@
require 'postal/config' require "postal/config"
if Postal.config.general&.exception_url if Postal.config.general&.exception_url
require 'raven' require "raven"
Raven.configure do |config| Raven.configure do |config|
config.dsn = Postal.config.general.exception_url config.dsn = Postal.config.general.exception_url
config.environments = ['production'] config.environments = ["production"]
if ENV['DEV_EXCEPTIONS'] if ENV["DEV_EXCEPTIONS"]
config.environments << 'development' config.environments << "development"
end end
config.silence_ready = true config.silence_ready = true
config.tags = {:process => ENV['PROC_NAME']} config.tags = { process: ENV.fetch("PROC_NAME", nil) }
end end
end end
+1 -1
View File
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
Rails.application.config.session_store :cookie_store, key: '_postal_session' Rails.application.config.session_store :cookie_store, key: "_postal_session"
+2 -2
View File
@@ -1,5 +1,5 @@
require 'postal/config' require "postal/config"
if Postal.config&.smtp if Postal.config&.smtp
ActionMailer::Base.delivery_method = :smtp ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {:address => Postal.config.smtp.host, :user_name => Postal.config.smtp.username, :password => Postal.config.smtp.password, :port => Postal.config.smtp.port || 25} ActionMailer::Base.smtp_settings = { address: Postal.config.smtp.host, user_name: Postal.config.smtp.username, password: Postal.config.smtp.password, port: Postal.config.smtp.port || 25 }
end end
+6 -2
View File
@@ -1,10 +1,13 @@
class Net::SMTP::Response class Net::SMTP::Response
def message def message
@string @string
end end
end end
class Net::SMTP class Net::SMTP
attr_accessor :source_address attr_accessor :source_address
def secure_socket? def secure_socket?
@@ -19,7 +22,7 @@ class Net::SMTP
# #
def rset def rset
@error_occurred = false @error_occurred = false
getok('RSET') getok("RSET")
end end
def rset_errors def rset_errors
@@ -29,6 +32,7 @@ class Net::SMTP
private private
def tcp_socket(address, port) def tcp_socket(address, port)
TCPSocket.open(address, port, self.source_address) TCPSocket.open(address, port, source_address)
end end
end end
+4
View File
@@ -1,9 +1,13 @@
module Rack module Rack
class Request class Request
module Helpers module Helpers
def trusted_proxy?(ip) def trusted_proxy?(ip)
ip =~ /^127\.0\.0\.1$|^localhost$|^unix$$/i ip =~ /^127\.0\.0\.1$|^localhost$|^unix$$/i
end end
end end
end end
end end
+4 -4
View File
@@ -1,9 +1,9 @@
require_relative '../lib/postal/config' require_relative "../lib/postal/config"
threads_count = Postal.config.web_server&.max_threads&.to_i || 5 threads_count = Postal.config.web_server&.max_threads&.to_i || 5
threads threads_count, threads_count threads threads_count, threads_count
bind_address = Postal.config.web_server&.bind_address || '127.0.0.1' bind_address = Postal.config.web_server&.bind_address || "127.0.0.1"
bind_port = Postal.config.web_server&.port&.to_i || ENV['PORT'] || 5000 bind_port = Postal.config.web_server&.port&.to_i || ENV["PORT"] || 5000
bind "tcp://#{bind_address}:#{bind_port}" bind "tcp://#{bind_address}:#{bind_port}"
environment Postal.config.rails&.environment || 'development' environment Postal.config.rails&.environment || "development"
prune_bundler prune_bundler
quiet false quiet false

Some files were not shown because too many files have changed in this diff Show More