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